Merge multiple sources into a single destination - c#

I want to combine 2 Domain Objects into a single data transfer object using AutoMapper.
Domain Model:
public class Service {
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<DownloadService> DownloadServices { get; set; } = new HashSet<DownloadService>();
}
public class DownloadService {
public int Id { get; set; }
public int PageLimit { get; set; }
public virtual int ServiceId { get; set; }
public virtual Service Service { get; set; }
}
public class Volume {
public override int Id { get; set; }
public bool IsActive { get; set; }
public string Path { get; set; }
public string Description { get; set; }
}
DTO:
public class PreferenceVM {
public ICollection<VolumeVM> Volumes { get; set; }
public ICollection<ServiceVM> Services { get; set; }
}
public class ServiceVM {
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<DownloadServiceVM> DownloadServices { get; set; } = new HashSet<DownloadServiceVM>();
}
public class DownloadServiceVM {
public int Id { get; set; }
public int PageLimit { get; set; }
public int CleaningInterval { get; set; }
}
public class VolumeVM {
public int Id { get; set; }
public bool IsActive { get; set; }
public string Path { get; set; }
public string Description { get; set; }
}
Mapping:
cfg.CreateMap<Volume, VolumeVM>().ReverseMap();
cfg.CreateMap<DownloadService, DownloadServiceVM>().ReverseMap();
cfg.CreateMap<Service, ServiceVM>()
.ForMember(d => d.DownloadServices, opt => opt.MapFrom(s => s.DownloadServices))
.ReverseMap();
cfg.CreateMap<ICollection<Volume>, PreferenceVM>()
.ForMember(x => x.Volumes, y => y.MapFrom(src => src)).ReverseMap();
cfg.CreateMap<ICollection<Service>, PreferenceVM>()
.ForMember(x => x.Services, y => y.MapFrom(src => src)).ReverseMap();
when I try the mapping above:
var services = serviceRepository.GetAll();
var volumes = volumeRepository.GetAll();
var entities = mapper.Map<PreferenceVM>(services);
entities = mapper.Map(volumes, entities);
I get the following errors:
Missing type map configuration or unsupported mapping.
Mapping types: EntityQueryable1 -> PreferenceVM
Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1[[Fwims.Core.Data.Model.Setting.Service,
Fwims.Core.Data.Model, Version=1.0.1.10, Culture=neutral,
PublicKeyToken=null]] -> Fwims.Core.ViewModel.Setting.PreferenceVM
It looks like my mapping is wrong, nothing I have tried has worked. How do I properly map the Domain objects to the Data transfer objects?

Here
cfg.CreateMap<ICollection<Volume>, PreferenceVM>()
.ForMember(x => x.Volumes, y => y.MapFrom(src => src)).ReverseMap();
and
cfg.CreateMap<ICollection<Service>, PreferenceVM>()
.ForMember(x => x.Services, y => y.MapFrom(src => src)).ReverseMap();
you create mappings from ICollection<TSource>.
However later on you are trying to map IQeryable<TSource>. While AutoMapper can use a base mapping to map a derived class, IQueryable<T> does not derive from ICollection<T>, hence the missing type map exception.
The solution is to create a mapping from some common base interface of IQueryable<T> and ICollection<T>, which is IEnumerable<T>.
So replace the above with:
cfg.CreateMap<IEnumerable<Volume>, PreferenceVM>()
.ForMember(x => x.Volumes, y => y.MapFrom(src => src));
cfg.CreateMap<IEnumerable<Service>, PreferenceVM>()
.ForMember(x => x.Services, y => y.MapFrom(src => src));
and the current issue will be solved.
Note that ReverseMap does not work in such scenarios, so I've just removed it. If you need such functionality, you have to create that mappings manually (eventually using ConvertUsing because there is no destination member).

Related

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);))

Automapper map Generics types

I have 2 class Data and MainData that I need to map
public class Data
{
public Field<string> Results { get; set; }
public Field<int> A { get; set; }
public Field<int> B { get; set; }
}
public class Field<T>
{
public string Label { get; set; }
public T Value { get; set; }
}
public class MainData{
public string result {get; set; }
public int a {get; set; }
public int b {get; set; }
}
I've tried to map Data to MainData using automapper as follows:
Mapper.CreateMap(Data, MainData))
.ForMember(dest => dest.result, conf => conf.MapFrom(src => src.results.Value))
.ForMember(dest => dest.a, conf => conf.MapFrom(src => src.A.Value))
.ForMember(dest => dest.b, conf => conf.MapFrom(src => src.B.Value))
var destination = Mapper.Map<Data, MainData>(source);
It's never get mapped value to desination variable. All i get is new MainData object.

Automapper complex object mapping NullReference exception

I have the following classes
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<GroupTier> Tiers { get; set; }
}
public class GroupTier : IEntity
{
public int Id { get; set; }
public int GroupId { get; set; }
public int Tier { get; set; }
public decimal Amount { get; set; }
public virtual Group Group { get; set; }
}
I am trying to map to the following ViewModel
public class GroupViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<decimal> Tiers { get; set; }
}
using the configuration
configuration.CreateMap<Group, GroupViewModel>()
.ForMember(m => m.Tiers, opt => opt.MapFrom(u => u.Tiers.OrderBy(q => q.Tier).Select(q => q.Amount)));
I am using EF6 to query from the database. I am having trouble when the Group.Tiers is null. How can I handle the null value?
When I use the this configuration
configuration.CreateMap<Group, GroupViewModel>()
.ForMember(m => m.Tiers, opt => opt.MapFrom(u => u.Tiers == null ? new List<decimal>() : u.Tiers.OrderBy(q => q.Tier).Select(q => q.Amount)));
I am getting this error
Cannot compare elements of type 'System.Collections.Generic.ICollection'

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