Automapper foces subobjects to be mapped - c#

I am trying to map a response object from webservice to a Class in my project. I thought the Automapper will map even the sub objects automatically, it does not unless and until is forcefully set for the member. Why should i do this ?
Mapper.CreateMap<GetIfpQuoteResponse.Quote, QuoteWSModel>()
.ForMember(dest => dest.CarrierRate, opt => opt.MapFrom(src => src.Carriers))
.ForMember(dest => dest.DroppedCarriers, opt => opt.MapFrom(src => src.DroppedRates))
.ForMember(dest => dest.MemberPlans, opt => opt.MapFrom(src => src.MemberPlans));
Why Wont the automapper map my su bobjects when i mention the class mapping like this
Mapper.CreateMap<GetIfpQuoteResponse.Quote, QuoteWSModel>();
Mapper.CreateMap<GetIfpQuoteResponse.Quote.Carrier, CarrierRateModel>();
Mapper.CreateMap<GetIfpQuoteResponse.Quote.DroppedCarrier, DroppedCarrierModel>();

AutoMapper only map top level object.
If your class is built in the following way it will not work:
Class A
{
B b;
}
Class B
{
}
Class A will not know how to map property B inside class A.
To do this you will need to create a Profile class.
Auto Mapper tutorial

Related

How to override Automapper Profile

I am using Automapper on my dotnet core project on which I have two profiles. One profile is use for common Mapping and second two override if some mapping is defined on that profile.
Lets say I have one profile:
public class CommonProfile : Profile
{
public CommonProfile(){
CreateMap<Product, ProductDto>()
.ForMember(dest => dest.ProductId, options => options.MapFrom(src => src.Id))
.ForMember(dest => dest.Title, options => options.MapFrom<Description>())
.ForMember(dest => dest.Price, options => options.MapFrom(src => src.Amount))
.AfterMap<ProductAfterMap>()
}
}
Now I have another profile on which some different mapping is defined for same classes with different properties like below:
public class UniqueProfile : Profile
{
public UniqueProfile(){
CreateMap<Product, ProductDto>()
.ForMember(dest => dest.Title, options => options.MapFrom<Name>())
}
}
In the first profile Title was mapped with Description but on the second profile it was mapped with Name. Now I have to use the second mapping for Title Property.
There are some more properties which requires different mapping in unique mapping, also custom resolovers.
You can use the CreateMap method on the UniqueProfile class to override the mapping defined in the CommonProfile class. The CreateMap method in the UniqueProfile class will have higher priority and will be used to perform the mapping.
Here's an example:
public class CommonProfile : Profile
{
public CommonProfile()
{
CreateMap<Product, ProductDto>()
.ForMember(dest => dest.ProductId, options => options.MapFrom(src => src.Id))
.ForMember(dest => dest.Title, options => options.MapFrom<Description>())
.ForMember(dest => dest.Price, options => options.MapFrom(src => src.Amount))
.AfterMap<ProductAfterMap>();
}
}
public class UniqueProfile : Profile
{
public UniqueProfile()
{
CreateMap<Product, ProductDto>()
.ForMember(dest => dest.Title, options => options.MapFrom<Name>())
// Add other custom mapping rules here
.AfterMap<UniqueProductAfterMap>();
}
}
In your application, make sure you are only using the UniqueProfile class while performing the mapping. You can do this by creating an instance of MapperConfiguration and passing in the UniqueProfile class when configuring the Automapper.

How not to explicitly specify members for which the name automatically match up?

I am using AutoMapper 9.0 and in the example below, I am mapping a Person to a People Object.
Because 1 member differs from Person to People (Person has Sfx while People has Suffix), I have to specifically map the rest of the properties that would otherwise automatically match up.
Is there a way to not specify them but for them to still be mapped ?
configurationExpression.CreateMap<JsonRequest, XmlRequest>()
.ForMember(
dest => dest.People,
opt => opt.MapFrom(src => new People
{
FirstName = src.Person.FirstName,
MiddleName = src.Person.MiddleName,
LastName = src.Person.LastName,
Suffix = src.Person.Sfx
}));
Checking the documentation, you should be able to achieve this by defining separate mappings for your OutterClass and InnerClass:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<OuterSource, OuterDest>();
cfg.CreateMap<InnerSource, InnerDest>();
});
Have you tried something like this?
configurationExpression.CreateMap<Person, People>()
.ForMember(dest => dest.Suffix, opt => opt.MapFrom(src => src.sfx))
.ReverseMap();
configurationExpression.CreateMap<JsonRequest, XmlRequest>()
.ForMember(dest => dest.People, opt => opt.MapFrom(src => src.Person))
.ReverseMap();

Automapper missing type when mapping database model from entity to viewmodel

I am a newbie in using automapper and I want to implement it in my project. I am trying to map multiple model from EF to single viewmodel in asp project but before doing that I have encountered a problem as below.
I tried to follow solution provided as:
Automapper missing type map configuration or unsupported mapping
Automapper missing type map configuration or unsupported mapping?
but without any success.
I am using recent automapper.
I tried variation of method to create map such as
config.CreateMap<tblMeeting, MeetingViewModels>()
.ForMember(dest => dest.meetingDetails, input => input.MapFrom(i => new tblMeeting
{
meetingId = i.meetingId,
meetingType = i.meetingType??null,
startTime = i.startTime,
finishTime = i.finishTime,
meetingDate = i.meetingDate,
meetingNotes = i.meetingNotes,
meetingVenue = i.meetingVenue
}));
and this
config.CreateMap<tblMeeting, MeetingViewModels>()
.ForMember(dest => dest.meetingDetails.meetingId, opt => opt.MapFrom(s => s.meetingId))
.ForMember(dest => dest.meetingDetails.startTime,
opt => opt.MapFrom((s => s.startTime)))
.ForMember(dest => dest.meetingDetails.finishTime,
opt => opt.MapFrom(s => s.finishTime))
.ForMember(dest => dest.meetingDetails.meetingType,
opt => opt.MapFrom(s => s.meetingType ?? null))
.ForMember(dest => dest.meetingDetails.meetingDate,
opt => opt.MapFrom(s => s.meetingDate))
.ForMember(dest => dest.meetingDetails.meetingVenue,
opt => opt.MapFrom(s => s.meetingVenue))
.ForMember(dest => dest.meetingDetails.meetingNotes,
opt => opt.MapFrom(s => s.meetingNotes));
});
this also
config.CreateMap<tblMeeting, MeetingViewModels>().ConvertUsing<test();
public class test : ITypeConverter<tblMeeting, MeetingViewModels>
{
public MeetingViewModels Convert(tblMeeting source, MeetingViewModels destination, ResolutionContext context)
{
MeetingViewModels m = new MeetingViewModels();
m.meetingDetails.meetingId = Guid.Parse(source.meetingType.ToString());
m.meetingDetails.meetingNotes = source.meetingNotes;
m.meetingDetails.meetingType = Guid.Parse(source.meetingType.ToString());
m.meetingDetails.meetingDate = source.meetingDate;
m.meetingDetails.startTime = source.startTime;
m.meetingDetails.finishTime = source.finishTime;
m.meetingDetails.meetingVenue = source.meetingVenue;
return m;
}
}
but non could solve the problem.
if anyone could help me out it would be of great help.
Thank you.
Here is how I personally implement AutoMapper in my projects:
First create a MappingConfig class, generally I put it in App_Code folder.
In my projects I probably have different sections in the system, by section I mean different Areas or somehow the application needs to be logically separated in different parts like User Management, Meetings etc whatever you have there...
So from the moment that I can divide the system in logical sections I create a profile class for each section:
Here is an example of profile class:
public class GeneralMappingConfigProfile : Profile
{
public GeneralMappingConfigProfile()
{
CreateMap<sourceObject, destinationObject>()
.ForMember(d => d.X, o => o.MapFrom(s => s.Y))
}
}
The class above is an example for general mappings but you may have there a Meetings profile class if it is big enough to be distinguished as a section.
Then in my config class I configure all profile classes as below:
public class MappingConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(config =>
{
config.AddProfile<GeneralMappingConfigProfile>();
config.AddProfile<MeetingsMappingConfigProfile>();
//etc
});
}
}
In the global.asax I call the static method like below:
MappingConfig.RegisterMappings();
Then I can create mappings as many as I see fit in each profile:
I just wrote all this code so you can organize the code better...
For your situation might be a lot of things that might cause this error but refer to this question here. Can you please share more code here because would like to see the MeetingViewModels model and the action code because there must be something wrong at the way how you get the tblMeeting object from database.

Can Automapper use an entity's navigation properties to pull values from related entities

Is Automapper able to drill down into an entityy navigation properties to map to a DTO class? Below is what I am doing to map results from an Entity Framework query to a DTO:
public List<ProductRequestDetailDto> GetProductRequestExtendedDetailAll()
{
List<ProductRequest> aProductRequestList = unitOfWork.getProductRequestRepository().GetProductRequestExtendedDetailAll();
List<ProductRequestDetailDto> ProductRequestDetailDtoList = new List<ProductRequestDetailDto>();
foreach (ProductRequest Req in aProductRequestList)
{
ProductRequestDetailDto ProdReqDetDto = new ProductRequestDetailDto();
ProdReqDetDto.ProductRequestId = Req.ProductRequestId;
ProdReqDetDto.FirstName = Req.Employee.FirstName;
ProdReqDetDto.MiddleInitial = Req.Employee.MiddleInitial;
ProdReqDetDto.LastName = Req.Employee.LastName;
ProdReqDetDto.DeptName = Req.Employee.Department.DeptName;
ProdReqDetDto.DeviceType = Req.ProductProfile.DeviceType;
ProdReqDetDto.ProductName = Req.ProductProfile.ProductName;
ProdReqDetDto.ProductId = Req.ProductProfile.ProductId;
ProdReqDetDto.ProductRequestStageId = Req.ProductRequestStage.ProductRequestStageId;
ProdReqDetDto.DateRequested = Req.DateRequested;
ProdReqDetDto.DateCompleted = Req.DateCompleted;
ProdReqDetDto.SerialNumber = Req.SerialNumber;
ProdReqDetDto.PhoneNumber = Req.PhoneNumber;
ProductRequestDetailDtoList.Add(ProdReqDetDto);
}
return ProductRequestDetailDtoList;
public List<ProductRequest> GetProductRequestExtendedDetailAll()
{
var ReportResult = from Req in context.ProductRequests
select Req;
return ReportResult.ToList();
}
I would like to avoid doing the above if Automapper can do it for me. Automapper has been able to map results to my DTOs when I don't need to drill down to the navigation properties of an entity which leads to other entities. I tried the following but it did not work probably because I need information that requires navigating to other entities such as Employee, Department, and ProductProfile:
List<ProductRequestDetailDto> ProductRequestDetailDtoList = Mapper.Map<List<ProductRequestDetailDto>>(aProductRequestList);
If this can be done what is the correct way to do it?
Have you look in to the extension Queryable Extensions for auto mapper it has an extension .ProjectTo that might help you with what you trying to achieve otherwise you will need to create a mapping configuration for the given case.
with something like
AutoMapper.Mapper.CreateMap<ProductRequestDetailDto, Req>()
.ForMember(dest => dest.FirstName ,
opts => opts.MapFrom(src => src.Employee.FirstName));
No, it cannot drill down into the properties because it cannot know how deep it should get or what to do with ambiguities.
By default it only maps automatically the properties with the same name, so this would already save you some code there but you would still need to teach it how to map the other properties. It can also map a list to another if the types they hold have a mapping to each other (which would remove the need for a foreach loop).
Note that if the properties with the same name don't have the same type you will also need to add a mapping for them (if they are not castable to each other).
public List<ProductRequestDetailDto> GetProductRequestExtendedDetailAll()
{
AutoMapper.Mapper.CreateMap<ProductRequest, ProductRequestDetailDto>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.Employee.FirstName))
.ForMember(dest => dest.MiddleInitial, opt => opt.MapFrom(src => src.Employee.MiddleInitial))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Employee.LastName))
.ForMember(dest => dest.DeptName, opt => opt.MapFrom(src => src.Employee.Department.DeptName))
.ForMember(dest => dest.DeviceType, opt => opt.MapFrom(src => src.ProductProfile.DeviceType))
.ForMember(dest => dest.ProductName, opt => opt.MapFrom(src => src.ProductProfile.ProductName))
.ForMember(dest => dest.ProductId, opt => opt.MapFrom(src => src.ProductProfile.ProductId))
.ForMember(dest => dest.ProductRequestStageId, opt => opt.MapFrom(src => src.ProductRequestStage.ProductRequestStageId));
IQueryable<ProductRequest> aProductRequestList = unitOfWork.getProductRequestRepository().GetProductRequestExtendedDetailAll();
List<ProductRequestDetailDto> ProductRequestDetailDtoList = aProductRequestList.ProjectTo<ProductRequestDetailDto>().ToList();
// or also
// List<ProductRequestDetailDto> ProductRequestDetailDtoList = aProductRequestList.Select(AutoMapper.Mapper.Map<ProductRequestDetailDto>).ToList();
return ProductRequestDetailDtoList;
}
public IQueryable<ProductRequest> GetProductRequestExtendedDetailAll()
{
var ReportResult = from Req in context.ProductRequests
select Req;
return ReportResult;
}

Automapper configuration to ignore a destination class's base member

ViewModel:
Class BaseViewModel
{
public string ViewOnlyProperty{get; set;}
...
}
Class VmClass<T,A>:BaseViewModel where T:IVmShared A:IVmSpecific
{
...
}
DomainModel:
Class BaseDomainModel
{
...
}
Class DomainClass<T,A>:BaseDomainModel where T:IDomainShared A:IDomainSpecific
{
...
}
Automapper:
Mapper.CreateMap<BaseDomainModel, BaseViewModel>()
.Include<IDomainClass<IDomainShared,IDomainSpecific>, VmClass<IVmShared,IVmSpecific>>()
// Included other concrete classes implements Domain and View models interfaces
.ForMember(x => x.ViewOnlyProperty, opt => opt.Ignore());
Mapper.CreateMap<IDomainClass<IDomainShared,IDomainSpecific>, VmClass<IVmShared,IVmSpecific>>()
.ForMember(x => x.ViewOnlyProperty, opt => opt.Ignore()).ReverseMap();
I have the same configurations for ViewModel to Domain mappings. But I am getting the below exception after Mapper.AssertConfigurationIsValid();
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
VmClass`2 -> DomainClass`2 (Source member list)
VmClass`2[[IVmShared],[IVmSpecific]] -> DomainClass`2[[IDomainShared],[IDomainSpecific]] (Source member list)
---------------------------------------------------------------------------------------
ViewOnlyProperty
Why I am getting exception for viewmodel property when it's validating viewModel->DomainModel mapping, where destination doesn't have this property at all in the base class?
I have found the cause for this issue. It was:
Mapper.CreateMap<IDomainClass<IDomainShared,IDomainSpecific>, VmClass<IVmShared,IVmSpecific>>()
.ForMember(x => x.ViewOnlyProperty, opt => opt.Ignore()).ReverseMap();
ReverseMap() doesn't work as ViewOnlyProperty doesn't exist in the destination. But Automapper was trying to map the "ViewOnlyProperty" from domain to view model, because of the reverse mapping was done on the same CreateMap(). Issue is fixed by splitting the mapping to:
Mapper.CreateMap<IDomainClass<IDomainShared,IDomainSpecific>, VmClass<IVmShared,IVmSpecific>>()
.ForMember(x => x.ViewOnlyProperty, opt => opt.Ignore());
Mapper.CreateMap<VmClass<IVmShared,IVmSpecific>,IDomainClass<IDomainShared,IDomainSpecific>>();

Categories