Mapping Source Collection's Id to Destination Objects Collection Id - c#

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

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

Is there a way to do the mapping using Automapper

The TeacherSubjects list in TeacherVM always shows null even though Automapper is used to map SubjectVM to TeacherSubject.
I have tried the code below with the automapper configuration. SchoolName is pulling through but TeacherSubjectlist is always null.
public class Teacher
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Address { get; set; }
public School WorkingSchool { get; set; }
public int SchoolId { get; set; }
public List<TeacherSubject> TeacherSubjectslist { get; set; }
}
public class TeacherSubject
{
public int TeacherSubjectId { get; set; }
public Subject Subject { get; set; }
public int SubjectId { get; set; }
public Teacher Teacher { get; set; }
public int TeacherId { get; set; }
}
public class TeacherVM
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Address { get; set; }
public int SchoolId { get; set; }
public string SchoolName { get; set; }
public List<SubjectVM> TeacherSubjects { get; set; }
}
public class SubjectVM
{
public string SubjectName { get; set; }
public int SubjectId { get; set; }
}
CreateMap<domain.TeacherSubject, SubjectVM>()
.ForMember(dest => dest.SubjectName, opt => opt.MapFrom(src =>
src.Subject.SubjectName))
.ForMember(dest => dest.SubjectId, opt => opt.MapFrom(src =>
src.Subject.SubjectId));
CreateMap<domain.Teacher, TeacherVM>()
.ForMember(dest => dest.SchoolName, opt => opt.MapFrom(src =>
src.WorkingSchool.SchoolName))
.ForMember(dest => dest.TeacherSubjects, opt => opt.MapFrom(src
=> src.TeacherSubjectslist));
TeacherSubjectlist should be a list of the SubjectId and the SubjectName properties.
The problem is that you are missing creating instance of list types inside each class that contains list of objects.
What you need to do is add instance of list type in class constructor.
With out testing it, for example:
public Teacher()
{
TeacherSubjectslist = new List<TeacherSubject>();
..
..
The same concept is valid for the remaining classes that have list of objects.

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?

Merge multiple sources into a single destination

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

Categories