AutoMapper Include does not work as expected? - c#

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

Related

AutoMapper flattening

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

Can I centralize mapping which I want to ignore in AutoMapper?

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

AutoMapperConfigurationException: Custom configuration for members is only supported for top-level individual members on a type

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

Two-way mapping

I have the following mapping:
CreateMap<LayoutRequest, Layout>();
CreateMap<Layout, LayoutRequest>()
.ForMember(m => m.Settings, opt => opt.Ignore());
It works fine but are we able to do it by single line? I thought it should solve my case:
CreateMap<LayoutRequest, Layout>()
.ForSourceMember(m => m.Settings, opt => opt.Ignore());
But it doesn't work. It says there is no mapping for Settings
AutoMapper has a "ReverseMap" feature that can be used like below.
CreateMap<Layout, LayoutRequest>()
.ForMember(m => m.Settings, opt => opt.Ignore())
.ReverseMap();

Is it possible to automatically map all properties except a few complicated ones with AutoMapper?

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

Categories