"Missing map from System.Char to System.String" AutoMapper error - c#

I'm trying to map my Box object to my BoxedElectrodesRowModel.
The Box object has a property SerialNumbers, and each of those has its own property of SerialNumberName. I'm trying to map that list of SerialNumberNames to a list of strings in my BoxedElectrodeRowModel called SerialNumbers.
AutoMapper code
c.CreateMap<Box, BoxedElectrodesRowModel>()
.ForMember(dest => dest.BoxId, opts => opts.MapFrom(src => src.BoxID))
.ForMember(dest => dest.SerialNumbers, opts => opts.MapFrom(src => src.SerialNumbers.Select(t => t.SerialNumberName).FirstOrDefault().ToList()))
.ForMember(dest => dest.DateCreated, opts => opts.MapFrom(src => src.DateCreated));
Here's the error I'm getting now. I don't know what "characters" it's referring to.
Missing map from System.Char to System.String. Create using
Mapper.CreateMap
The purpose of all of this is to create a table using DataTables that displays each Box with a list of its SerialNumbers on each row, if that helps. Everything is coded, but I keep getting the above error when it's run.
Edit: Here are my classes I'm mapping. I'm trying to map BoxId to BoxId, DateCreated to DateCreated, and the SerialNumberName from each SerialNumber (in a list) to SerialNumbers.
Box (Autogenerated)
public partial class Box
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Box()
{
this.SerialNumbers = new HashSet<SerialNumber>();
}
public int BoxID { get; set; }
public System.DateTime DateCreated { get; set; }
public Nullable<System.DateTime> DateShipped { get; set; }
public string TrackingNumber { get; set; }
public Nullable<System.DateTime> DateReceived { get; set; }
public bool Active { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<SerialNumber> SerialNumbers { get; set; }
}
SerialNumber (This is a property of each 'Box', also autogenerated code)
public partial class SerialNumber
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public SerialNumber()
{
this.Comments = new HashSet<Comment>();
this.WIPHistories = new HashSet<WIPHistory>();
}
public int SerialNumberID { get; set; }
public int IncomingLotID { get; set; }
public string SerialNumberName { get; set; }
public string LamPurchaseOrder { get; set; }
public string LamLineNumber { get; set; }
public bool Refurbished { get; set; }
public int WIPLocationID { get; set; }
public int StatusID { get; set; }
public int RouteSectionStepID { get; set; }
public Nullable<int> RejectCategoryID { get; set; }
public Nullable<int> BoxID { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Comment> Comments { get; set; }
public virtual IncomingLot IncomingLot { get; set; }
public virtual RejectCategory RejectCategory { get; set; }
public virtual Status Status { get; set; }
public virtual WIPLocation WIPLocation { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WIPHistory> WIPHistories { get; set; }
public virtual Box Box { get; set; }
public virtual RouteSectionStep RouteSectionStep { get; set; }
}
BoxedElectrodesRowModel
public class BoxedElectrodesRowModel
{
public int BoxId { get; set; }
public List<string> SerialNumbers { get; set; } // change from List to ICollection if there are problems
public Nullable<System.DateTime> DateCreated { get; set; }
}

Your problem is FirstOrDefault() call. Because it returns only first string from serial number names. But string is IEnumerable<char>. When AutoMapper sees two enumerables, it tries to map them. In your case it will be IEnumerable<char> to IEnumerable<string>.
To fix this problem remove FirstOrDefault() call. Also thus AutoMapper knows how to map IEnumerable<T> to List<T> you don't need to create list manually.
opts => opts.MapFrom(src => src.SerialNumbers.Select(t => t.SerialNumberName))

I think the issue is highlighted in the error message.
Automapper is being told to map from a character object, System.Char, to a string object, System.String.
I believe the culprit is the call to FirstOrDefault(), which when called on a string, will return the first Char in the string:
src.SerialNumbers.Select(t => t.SerialNumberName).FirstOrDefault().ToList()
The select statement selects a String from the 'SerialNumbers' collection, which is what the 'MapFrom' call needs...so you don't need to call .FirstOrDefault or .ToList().
Remove them and see how this goes:
c.CreateMap<Box, BoxedElectrodesRowModel>()
.ForMember(dest => dest.BoxId, opts => opts.MapFrom(src => src.BoxID))
.ForMember(dest => dest.SerialNumbers, opts => opts.MapFrom(src => src.SerialNumbers.Select(t => t.SerialNumberName)))
.ForMember(dest => dest.DateCreated, opts => opts.MapFrom(src => src.DateCreated));
There is also another post about this issue:
AutoMapper: Collection to Single string Property

Related

Automapper and mapping complex collections

Hi I have a question about automapper, thing is I have a model that has nested collection of other models, and models in that collection also has a collection of models something like (DB model):
public class Cabin
{
public uint Id { get; set; }
public string Name { get; set; }
public Rack[] Racks { get; set; }
}
public class Rack
{
public uint Id { get; set; }
public string RackName { get; set; }
public IPAddress IpAddress { get; set; }
public int Port { get; set; }
public Module[] Modules { get; set; }
}
public class Module
{
public uint Id { get; set; }
public string ModuleName { get; set; }
}
Well from Dto side I have something like:
public class CabinDto
{
public uint Id { get; set; }
public string Name { get; set; }
public RackDto[] Racks { get; set; }
}
public class RackDto
{
public uint Id { get; set; }
public string Name { get; set; }
public ModuleDto[] Modules{ get; set; }
}
public class ModuleDto
{
public string Name { get; set; }
}
So I want to map it all at once, but figure out a way to map a list object with different properties names.
For main class I have:
CreateMap<Db.Cabin, Dto.Cabin>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name));
// how to map nested list
I could just add some method that assigns values and map to this method, but it does not feel right. I looked in documentation and there are only examples with simple collection with same name lists.
Is there a way to do it?
You just need to add mapping for the type in the nested list and AutoMapper will take care of it.
CreateMap<Db.Module, Dto.Module>();
CreateMap<Db.Rack, Dto.Rack>();
Also when the name of properties in source and origin are the same, you don't need to call the ForMember() method.
So in your case you need it for the ModuleName to Name of the Module to ModuleDto mapping, and same for the Rack class, see this fiddle.

How to flattening child object used repeatedly in C# with Automapper

Using the following entities
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
}
public class GeneralEntity
{
public Guid Id { get; set; }
public User CreatedByUser { get; set; }
public User DeletedByUser { get; set; }
}
How do I flatten this to the GeneralEntityDto below?
public class GeneralEntityDto
{
public Guid Id { get; set; }
public string CreatedByUsername { get; set; }
public string DeletedByUsername { get; set; }
}
I have tried setting up my mappings as seen below but it fails with a complaint about "CreatedByUsername" and "DeletedByUsername" not being mapped.
protected void Configure()
{
CreateMap<GeneralEntity, GeneralEntityDto>()
.ForMember(dest => dest.CreatedByUsername,
opt => opt.MapFrom(src => src.CreatedByUser.Username))
.ForMember(dest => dest.DeletedByUsername, opt =>
opt.MapFrom(src => src.DeletedByUser.Username));
}
You can use the naming convention that automapper provides.
Basically if you include the exact string of the property name of the source Object you do not have to add ForMember() automapper is clever enough to do it automatically.
That means for example :
public class GeneralEntity
{
public Guid Id { get; set; }
public User CreatedBy { get; set; } // renaming just for simplicity
public User DeletedBy { get; set; } // renaming just for simplicity
}
public class GeneralEntityDto
{
public Guid Id { get; set; }
public string CreatedByUsername { get; set; }
public string DeletedByUsername { get; set; }
}
Reference also to these:
http://docs.automapper.org/en/stable/Flattening.html
AutoMapper TwoWay Mapping with same Property Name

AutoMapper Infinite Loop using EF Code First

I have the following classes (One-One relationship Asset-TrackingDevice):
public class Asset
{
public int Id { get; set; }
public string Name { get; set; }
public TrackingDevice TrackingDevice { get; set; }
}
public class TrackingDevice
{
public int Id { get; set; }
public string Imei { get; set; }
public int? AssetId { get; set; }
public Asset Asset { get; set; }
}
The viewModels are very similar:
public class AssetViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public int? TrackingDeviceId { get; set; }
public TrackingDeviceViewModel TrackingDevice { get; set; }
}
public class TrackingDeviceViewModel
{
public int Id { get; set; }
public string Imei { get; set; }
public AssetViewModel Asset { get; set; }
public string AssetId { get; set; }
}
Mappings:
CreateMap<Asset, AssetViewModel>()
.ForMember(d => d.TrackingDevice, map => map.Ignore());
CreateMap<AssetViewModel, Asset>()
.ForMember(d => d.TrackingDevice, map => map.Ignore());
CreateMap<AssetViewModel, Asset>()
.ReverseMap();
CreateMap<TrackingDevice, TrackingDeviceViewModel>()
.ForMember(d => d.Asset, map => map.Ignore());
CreateMap<TrackingDeviceViewModel, TrackingDevice>()
.ForMember(d => d.Asset, map => map.Ignore());
CreateMap<TrackingDevice, TrackingDeviceViewModel>()
.ReverseMap();
When I perform a database query to obtain the TrackingDevices,
I get an error because in the mapping the Asset within Tracking Device also includes a Tracking Device and so on.
The query that I execute to obtain the tracking devices is:
var trackingDevices = _appContext.TrackingDevices
.Include(td => td.Asset)
.ToListAsync();
var trackingMapper = Mapper.Map<IEnumerable<TrackingDeviceViewModel>>(trackingDevices);
I read that by including the Map.Ignore would fix the problem but it did not work either, does anyone know what my error is?

C# AutoMapper with Entity Framework - Works with List but not single nullable instance

I have a very wierd error that I can't get my head around. I'm using AutoMapper 6 with AutoMapper.Collection and AutoMapper.Collection.EntityFramework.
https://github.com/AutoMapper/AutoMapper.Collection
As you can see from the screenshot below, every component is updated apart from Image that is null for updatedContact. If I however do an explicit mapping for only updatedImage it works. It also works to update a collection of images without a problem. Has anyone experienced this? Other single properties works as well but for some reason Image is causing trouble.
//Works
var updatedArticle = Mapper.Map<ArticleViewModel, Article>(articleVm, articleOriginal);
//Every component is updated a part from Image.
var updatedContact = Mapper.Map<ContactViewModel, Contact>(contactVm, contactOriginal);
//Works
var updatedImage = Mapper.Map<ImageViewModel, Image>(contactVm.Image);
//Works
var newContact = Mapper.Map<ContactViewModel, Contact>(contactVm);
Mapping:
cfg.CreateMap<ArticleViewModel, Article>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id);
cfg.CreateMap<ImageViewModel, Image>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForSourceMember(x => x.IsDeleted, opt => opt.Ignore())
.ForMember(dest => dest.ImageBytes, opt => opt.MapFrom(src => Encoding.ASCII.GetBytes(src.Image)));
cfg.CreateMap<ContactViewModel, Contact>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForSourceMember(x => x.IsDeleted, opt => opt.Ignore())
.ForSourceMember(x => x.FullName, opt => opt.Ignore());
Files:
public class ArticleViewModel
{
public int Id { get; set; }
...
public List<ImageViewModel> Images { get; set; }
}
public class Article : IEntity<int>
{
public int Id { get; set; }
...
public virtual ICollection<Image> Images { get; set; }
}
public class ContactViewModel
{
public int Id { get; set; }
...
public ImageViewModel Image { get; set; }
}
public class Contact: IEntity<int>
{
[Key]
public int Id { get; set; }
...
public int? ImageId { get; set; }
public Image Image { get; set; }
}
public class ImageViewModel
{
public int Id { get; set; }
public string Image { get; set; }
public string ImageType { get; set; }
public bool IsDeleted { get; set; }
}
public class Image : IEntity<int>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public byte[] ImageBytes { get; set; }
public string ImageType { get; set; }
public int? ArticleId { get; set; }
public virtual Article Article { get; set; }
}
Finally solved it, I had forgot to mark Image as virtual in Contact. After doing that everything started working out of the box.
public virtual Image Image { get; set; }
I think you need to tell your contact mapper in the config to explicitly use the mapping for the image vm. There might be one or two typos as I'm doing this from memory but it should be similar to:
.ForMember(x => x.Image, opt => opt.MapFrom(contact => Mapper.Map<ImageViewModel, Image>(contact.ImageVm);))

Mapping Source Collection's Id to Destination Objects Collection Id

Suppose in the source object I have classes:
// Source classes
class Source
{
public Source
{
things = new List<Thing>();
}
public Guid SourceId { get; set; }
public string Name { get; set; }
public virtual List<Thing> Things { get; set; }
}
class Thing
{
public Guid ThingId { get; set; }
public double Price { get; set; }
}
//Destination class
class Dest
{
public Guid DestId { get; set; }
public string Name { get; set; }
public virtual List<Guid> ThingsIds { get; set; }
}
How do I map Things -> ThingId (src) to ThingsIds (dest) using Automapper?
I would use the LINQ extension method .Select:
Mapper.CreateMap<Source, Dest>()
.ForMember(
dest => dest.ThingsIds,
opt => opt.MapFrom(src => src.Things.Select(th => th.ThingId)));

Categories