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
Related
I have classes which have the usual CreatedDate, CreatedById, EditedDate, EditedById and etc. fields on which I ignore when mapping from Domain models -> Entity models (they are populated later by the database).
My mappings are now littered with mapping ignore looking a bit like
CreateMap<UpdateProvider, ProviderInstance>()
.ForMember(e => e.Id, opt => opt.Ignore())
.ForMember(e => e.CreatedDate, opt => opt.Ignore())
.ForMember(e => e.CreatedById, opt => opt.Ignore())
.ForMember(e => e.EditedDate, opt => opt.Ignore())
.ForMember(e => e.EditedById, opt => opt.Ignore())
.ForMember(e => e.ArchivedDate, opt => opt.Ignore())
.ForMember(e => e.ArchivedById, opt => opt.Ignore());
Can I centralize this in a single method to adhere to the DRY principle. If it helps each of these fields have interfaces called IIdentifiable, ICreateable, IEditable, IArchiveable.
I was hoping to just create an extension method of something like .IgnoreDbGeneratedFields<TDestination>()
An extension method along the following lines should work:
public static IMappingExpression<T1, T2> IgnoreDbGeneratedFields<T1, T2>(this IMappingExpression<T1, T2> e)
where T2 : IIdentifiable, ICreateable, IEditable, IArchiveable
{
return e
.ForMember(x => x.Id, opt => opt.Ignore())
.ForMember(x => x.CreatedDate, opt => opt.Ignore())
.ForMember(x => x.CreatedById, opt => opt.Ignore())
.ForMember(x => x.EditedDate, opt => opt.Ignore())
.ForMember(x => x.EditedById, opt => opt.Ignore())
.ForMember(x => x.ArchivedDate, opt => opt.Ignore())
.ForMember(x => x.ArchivedById, opt => opt.Ignore());
}
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)))
;
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
I have some objects with a large number of properties. When I started using AutoMapper I was attempting to just map a few nested objects to understand how it works. All the time I thought it would map over properties with the same name at the source and the destination. However it seems that once I have some ForMember() rules it only maps the properties for which I have rules.
Is there a way to only a specify a few rules, where the source and destination object differ? Or do I have to be explicit and map every property?
Mapper.CreateMap<MessageWireFormat, Message>()
.ForMember(dest => dest.PlainBody, opt => opt.MapFrom(src => src.Body.Plain))
.ForMember(dest => dest.ParsedBody, opt => opt.MapFrom(src => src.Body.Parsed))
.ForMember(dest => dest.RichBody, opt => opt.MapFrom(src => src.Body.Rich))
.ForMember(dest => dest.Excerpt, opt => opt.MapFrom(src => src.ContentExcerpt))
.ForMember(dest => dest.AttachmentCount, opt => opt.MapFrom(src => src.Attachments.Length))
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Url, opt => opt.MapFrom(src => src.Url))
.ForMember(dest => dest.WebUrl, opt => opt.MapFrom(src => src.WebUrl))
.ForMember(dest => dest.NetworkId, opt => opt.MapFrom(src => src.NetworkId))
.ForMember(dest => dest.GroupId, opt => opt.MapFrom(src => src.GroupId))
.ForMember(dest => dest.SystemMessage, opt => opt.MapFrom(src => src.SystemMessage))
.ForMember(dest => dest.Privacy, opt => opt.MapFrom(src => src.Privacy))
.ForMember(dest => dest.DirectMessage, opt => opt.MapFrom(src => src.DirectMessage))
.ForMember(dest => dest.SenderType, opt => opt.MapFrom(src => src.SenderType))
.ForMember(dest => dest.SenderId, opt => opt.MapFrom(src => src.SenderId))
.ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => src.CreatedAt))
.ForMember(dest => dest.ClientType, opt => opt.MapFrom(src => src.ClientType))
.ForMember(dest => dest.ClientUrl, opt => opt.MapFrom(src => src.ClientUrl))
.ForMember(dest => dest.RepliedToId, opt => opt.MapFrom(src => src.RepliedToId))
.ForMember(dest => dest.Language, opt => opt.MapFrom(src => src.Language));
Holy moly. Nearly all of those mappings are unnecessary. MapFrom is only intended when names don't match, not when they do. Even when they don't match, I'd need a really good reason to not have "BodyPlain", which would map without configuration, instead of "PlainBody", which doesn't match "Body.Plain".