I have a ViewModel that needs data from 2 collections. The 2 collections are members of Indicatiestelling. So to map this I pass an instance of Indicatiestelling.
Each property uses a ValueResolver that gets the right value out of the given collection. To make this work I need to register the ValueResolver for each property and the source for each property. I tried to do this:
Mapper.CreateMap<Model.Indicatiestelling, ClientRechtmatigheidDto>()
.ForMember(dest => dest.HasFactBeoordelenRechtmatigheid, (opt) => { opt.ResolveUsing<IndicatiestellingFactValueResolver>(); opt.MapFrom(src => src.IndicatiestellingFacts); })
.ForMember(dest => dest.HasFactRechtmatig, (opt) => { opt.ResolveUsing<IndicatiestellingFactValueResolver>(); opt.MapFrom(src => src.IndicatiestellingFacts); })
.ForMember(dest => dest.SoortVoorziening, (opt) => { opt.ResolveUsing<IndicatiestellingAnswerValueResolver>(); opt.MapFrom(src => src.IndicatiestellingAnswer); })
.ForMember(dest => dest.ZZP, (opt) => { opt.ResolveUsing<IndicatiestellingAnswerValueResolver>(); opt.MapFrom(src => src.IndicatiestellingAnswer); });
This code doesn't work, I still get mapping errors:
Missing type map configuration or unsupported mapping.
Mapping types: HashSet`1 -> Boolean
I searched for an example/docs about using multiple member options, nothing came up. Is it supported? And ifso, what am I doing wrong here?
I don't think they can be combined in the way you want, but with a little refactoring you can use this:
Mapper.CreateMap<Model.Indicatiestelling, ClientRechtmatigheidDto>()
.ForMember(dest => dest.HasFactBeoordelenRechtmatigheid, opt => opt.ResolveUsing(src => IndicatiestellingFactValueResolver.Resolve(src.IndicatiestellingFacts)))
.ForMember(dest => dest.HasFactRechtmatig, opt => opt.ResolveUsing(src => IndicatiestellingFactValueResolver.Resolve(src.IndicatiestellingFacts)))
.ForMember(dest => dest.SoortVoorziening, opt => opt.ResolveUsing(src => IndicatiestellingAnswerValueResolver.Resolve(src.IndicatiestellingAnswer)))
.ForMember(dest => dest.ZZP, opt => opt.ResolveUsing(src => IndicatiestellingAnswerValueResolver.Resolve(src.IndicatiestellingAnswer)));
(I refactored your IValueResolvers into static methods)
Or you can make your IValueResolvers know which member to look at, e.g. hardcoded:
public class IndicatiestellingFactValueResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
var model = (Model.Indicatiestelling)source.Value;
var obj = model.IndicatiestellingFacts;
// calculate with obj
}
}
Mapper.CreateMap<Model.Indicatiestelling, ClientRechtmatigheidDto>()
.ForMember(dest => dest.HasFactBeoordelenRechtmatigheid, opt => opt.ResolveUsing<IndicatiestellingFactValueResolver<Model.Indicatiestelling>>())
// etc
Or using a Func:
public class IndicatiestellingFactValueResolver<TSource> : IValueResolver
{
private Func<TSource, object> selector;
public IndicatiestellingFactValueResolver(Func<TSource, object> selector)
{
this.selector = selector;
}
public ResolutionResult Resolve(ResolutionResult source)
{
var model = (TSource)source.Value;
object obj = selector(model);
// calculate with obj
}
}
Mapper.CreateMap<Model.Indicatiestelling, ClientRechtmatigheidDto>()
.ForMember(dest => dest.HasFactBeoordelenRechtmatigheid,
opt => opt.ResolveUsing<IndicatiestellingFactValueResolver<Model.Indicatiestelling>>()
.ConstructedBy(() => new IndicatiestellingFactValueResolver<Model.Indicatiestelling>(x => x.IndicatiestellingFacts)))
// etc
Related
I need to map source to destination with AutoMapper.
The types structure looks like this:
Source {
public string SourceField1;
public string SourceField2;
public InnerSource Inner;
}
InnerSource {
public string InnerSourceField3;
public string InnerSourceField4;
}
Destination {
public string DestinationField1;
public string DestinationField2;
public string DestinationField3;
public string DestinationField4;
}
My solution was looking like:
CreateMap<Source, Destination>()
.ForMember(dest => dest.DestinationField1, opt => opt.MapFrom(src => src.SourceField1))
.ForMember(dest => dest.DestinationField2, opt => opt.MapFrom(src => src.SourceField2))
.AfterMap((src, dest, context) => context.Mapper.Map(src.Inner, dest));
CreateMap<InnerSource, Destination>()
.ForMember(dest => dest.DestinationField3, opt => opt.MapFrom(src => src.InnerSourceField3))
.ForMember(dest => dest.DestinationField4, opt => opt.MapFrom(src => src.InnerSourceField4))
And all this stuff seems to work, but not with EF and ProjectTo extension method, because AfterMap is not "compatible" with EF.
So my question is how to make this work with EF? Should I use some workarounds or is there another way to map this types structure without AfterMap?
CreateMap<Source, Destination>().IncludeMembers(s => s.Inner)
.ForMember(dest => dest.DestinationField1, opt => opt.MapFrom(src => src.SourceField1))
.ForMember(dest => dest.DestinationField2, opt => opt.MapFrom(src => src.SourceField2));
CreateMap<InnerSource, Destination>()
.ForMember(dest => dest.DestinationField3, opt => opt.MapFrom(src => src.InnerSourceField3))
.ForMember(dest => dest.DestinationField4, opt => opt.MapFrom(src => src.InnerSourceField4));
Also you should to know that you can deny prefix
I am getting this error and I do not understand why. I am trying to map a Client object to a ClaimClient object (shown below):
public class ClaimClient : Client
{
public int ClaimClientID { get; set; }
public bool IsContactable { get; set; }
}
As you can see the ClaimClient will contain all the client properties plus the additional properties shown above. My mapper:
x.CreateMap<Client, ClaimClient>();
The only thing I can think of is it's expecting me to define a mapping between Client and Client? Any help appreciated
EDIT:
Mapper now as below but still getting same error:
x.CreateMap<Client, ClaimClient>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.LastName))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
.ForPath(dest => dest.pAddress, opt => opt.MapFrom(src => src.pAddress))
.ForMember(dest => dest.IsLead, opt => opt.MapFrom(src => src.IsLead))
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.HasOptions, opt => opt.MapFrom(src => src.HasOptions))
.ForPath(dest => dest.Options, opt => opt.MapFrom(src => src.Options));
x.CreateMap<Client, Client>();
I (am forced to) use AutoMapper version 1.1.0.188. I have a base class SchufaBaseFeature and a derived class SchufaFeature.
public partial class SchufaFeature : SchufaBaseFeature
{//some code here}
Why is Include not working as I would expect it to ?
This is what I have done with AutoMapper (Mapping to DataBase):
//TODO include does not work as it should!
Mapper.CreateMap<SchufaBaseFeature, CFSCHUFAFEATURE>()
.ForMember(dest => dest.FEATUREWITHOUTBIRTHDATE, opt => opt.MapFrom(src => GetSpecified(EnumToBool(src.featureWithoutBirthdate), src.featureWithoutBirthdateSpecified)))
.ForMember(dest => dest.OWNFEATURE, opt => opt.MapFrom(src => GetSpecified(src.ownFeature, src.ownFeatureSpecified)))
.Include<SchufaFeature, CFSCHUFAFEATURE>()
;
Mapper.CreateMap<SchufaFeature, CFSCHUFAFEATURE>()
.ForMember(dest => dest.DATE, opt => opt.MapFrom(src => GetDate(src.date)))
.ForMember(dest => dest.AMOUNT, opt => opt.MapFrom(src => src.amount.amount))
.ForMember(dest => dest.AMOUNTCUR, opt => opt.MapFrom(src => src.amount.currency))
.ForMember(dest => dest.NUMBEROFINSTALLEMENTS, opt => opt.MapFrom(src => TryParseToInt(src.numberOfInstallments)))
.ForMember(dest => dest.INSTALLMENTTYPE, opt => opt.MapFrom(src => src.installmentType))
;
It is correctly mapping FEATUREWITHOUTBIRTHDATE and OWNFEATURE, but the Include isn't called. When I run debugger the second CreateMap is never called.
I have checked the Documentation here (AutoMapper Github Inheritance) and I still can't understand what I am doing wrong. What might be the problem ? Is it me or is there a bug in this version of AutoMapper ?
You might need to switch the order in which you create maps. Include has a....feature in which it checks existing maps for configuration. If that existing map isn't there yet, you'll need to switch the order.
This is all fixed in 5.0 of course but you're stuck :)
I solved my problem by using .ConstructUsing. In the first version of AutoMapper the Include does not work as expected, switching the order did nothing for me . Below is my source code:
Mapper.CreateMap<SchufaFeature, CFSCHUFAFEATURE>()
.ForMember(dest => dest.FEATUREWITHOUTBIRTHDATE, opt => opt.Ignore())
.ForMember(dest => dest.OWNFEATURE, opt => opt.Ignore())
.ForMember(dest => dest.DATE, opt => opt.MapFrom(src => GetDate(src.date)))
.ForMember(dest => dest.NUMBEROFINSTALLEMENTS, opt => opt.MapFrom(src => TryParseToInt(src.numberOfInstallments)))
.ForMember(dest => dest.INSTALLMENTTYPE, opt => opt.MapFrom(src => src.installmentType))
.ForMember(dest => dest.AMOUNT, opt => opt.Ignore())
.AfterMap((dto, cfschufafeature) => { Mapper.Map(dto.amount, cfschufafeature); })
;
Mapper.CreateMap<SchufaTextFeature, CFSCHUFAFEATURE>()
.ForMember(dest => dest.FEATUREWITHOUTBIRTHDATE, opt => opt.Ignore())
.ForMember(dest => dest.OWNFEATURE, opt => opt.Ignore())
;
//include does not work in this AutoMapper version as expected, that is why we use ConstructUsing
Mapper.CreateMap<SchufaBaseFeature, CFSCHUFAFEATURE>()
.ConstructUsing(feature =>
{
var schufaFeature = feature as SchufaFeature;
var schufaTextFeature = feature as SchufaTextFeature;
CFSCHUFAFEATURE result = new CFSCHUFAFEATURE();
if (schufaFeature != null)
Mapper.Map(schufaFeature, result);
if (schufaTextFeature != null)
Mapper.Map(schufaTextFeature, result);
return result;
})
.ForMember(dest => dest.FEATUREWITHOUTBIRTHDATE, opt => opt.MapFrom(src => GetSpecified(EnumToBool(src.featureWithoutBirthdate), src.featureWithoutBirthdateSpecified)))
.ForMember(dest => dest.OWNFEATURE, opt => opt.MapFrom(src => GetSpecified(EnumToBool(src.ownFeature), src.ownFeatureSpecified)))
;
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;
}
I want to create an Automapper extension method that performs a custom mapping on all properties of a certain type. Here is some pseudocode demonstrating what I want:
public static IMappingExpression<TSource, TDestination> UnflattenMembers<TSource, TDestination>
(this IMappingExpression<TSource, TDestination> map)
{
foreach (property of the TSource Class)
{
if (property is of Type SpecialClass)
{
map.ForMember(dest => dest.property, opt => opt.MapFrom(src => src));
}
}
return map;
}
I want to do this to achieve a generic implementation of sydneyos's answer to this question: Using AutoMapper to unflatten a DTO.
E.g., Instead of doing this:
Mapper.CreateMap<SourceClass, DestClass>()
.ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )))
.ForMember(dest => dest.MailingInfo, opt => opt.MapFrom( src => src )))
.ForMember(dest => dest.Calendar, opt => opt.MapFrom( src => src )))
etc...;
I want to to this:
Mapper.CreateMap<SourceClass, DestClass>().UnflattenMembers();