Automapper: use default mapping for all other members - c#

I have a lot of classes to map from IDataReader like this:
class Dest
{
public string Field1;
public string Field2;
public string Field3;
}
...
public DestProfile
{
CreateMap<IDataReader, Dest>()
.ForMember(d => d.Field1, opt => opt.MapFrom(/*Do some custom convertion here*/)
.ForAllOtherMembers(/*Do default convention-based mapping for all other members/*)
}
So i'd like to perform custom convertion on selected fields and do default mapping without explicitly coding.
Question looks very common, but I didn't find how to achieve this.

So the most straightforward way is to write like this:
.ForAllOtherMembers(opt => opt.MapFrom(src => src[opt.DestinationMember.Name]))
But there are some caveats. For example
.IncludeBase<IDataReader, Base>()
.ForAllOtherMembers(opt => opt.MapFrom(src => src[opt.DestinationMember.Name]))
Here ForAllOtherMembers will override your base class definition.

Related

Automapper 2 source fields mapped to 1 destination field

I have 2 classes to be mapped:
class1 has fields PaymentState and PaymentStateId
public int PaymentStateId { get; set; }
[ForeignKey(nameof(PaymentStateId))]
[InverseProperty(nameof(PaymentStateEntity.OrderEntities))]
public virtual PaymentStateEntity PaymentState { get; set; }
class2 has field with the same name PaymentState but of enum type
public PaymentState PaymentState { get; set; }
while mapping class1 to class2 there is an error that field PaymentState cannot be mapped:
Unable to create a map expression from
class1.PaymentState (Entities.PaymentStateEntity) to PaymentState.PaymentState (Enums.PaymentState)
Mapping types:
class1-> class2
Destination Member:
PaymentState
have tried custom mapping fields, but I guess that the fact that there are 2 fields now which are to be mapped to 1 destication field makes the problem
CreateMap<class1, class2>()
.ForMember(dest => dest.PaymentState, opt => opt.MapFrom(src => src.PaymentStateId))
What is the way to ignore one source field though let another source field to be mapped to destination field?
I don't think the error cause is 2 fields mapped into 1 destination. Probably because of the difference of type between dest and source. You should check log what the error is.
But you can try to use
CreateMap<class1, class2>()
.ForMember(dest => dest.PaymentState, opt => opt.Ignore())
.ForMember(dest => dest.PaymentState, opt => opt.MapFrom(src => src.PaymentStateId))
Besides, you can convert 2 enums by setting their items to have the same value
.ForMember(dest => dest.PaymentState, opt => opt.MapFrom(src => (PaymentState)((int)src.PaymentState)))

Automapper : how to eleminate repetitive mapping?

I have following structure for tables. The two tables have a lot of common properties over 20 im just listing a two. Also I have 10 tables similar to this. This is how the tables are in the database. There is over 10 concrete tables with similar properties and are not connected to each other in any way. I am using POCO generator to generate the classes from my database.
public class A
{
public string name {get;set;}
public string address {get;set;}
public string AId {get;set;}
}
public class B
{
public string name {get;set;}
public string address {get;set;}
public string BId {get;set;}
}
I have following viewModels:
public class BaseViewModel
{
public string Fullname {get;set;}
public string Fulladdress {get;set;}
}
public class AviewModel : BaseViewModel
{
public string AId {get;set;}
}
public class BViewModel : BaseViewModel
{
public string BId {get;set;}
}
when I create mapping i have to repeat all this for each viewModel that I have created.
config.CreateMap<A, AviewModel>()
.ForMember(dest => Fulladdress, opt => opt.MapFrom(src =>.address))
.ForMember(dest => Fullname, opt => opt.MapFrom(src =>.name)).ReverseMap();
config.CreateMap<B, BviewModel>()
.ForMember(dest => Fulladdress, opt => opt.MapFrom(src =>.address))
.ForMember(dest => Fullname, opt => opt.MapFrom(src =>.name)).ReverseMap();
Is it possible to reduce the repetitive mappings that I might potentially have to do ?
You can move the common mapping code to a helper generic method. You will constrain the TDestination type to be a class derived from BaseViewModel, thus allowing to access the destination members in ForMember method. And for source mapping you will use the MapFrom overload accepting string property name:
public static class CommonMappings
{
public static IMappingExpression<TSource, TDestination> MapToBaseViewModel<TSource, TDestination>(this IMappingExpression<TSource, TDestination> map)
where TDestination : BaseViewModel
{
return map
.ForMember(dest => dest.Fulladdress, opt => opt.MapFrom("address"))
.ForMember(dest => dest.Fullname, opt => opt.MapFrom("name"));
}
}
Then you can use it like this:
config.CreateMap<A, AViewModel>()
.MapToBaseViewModel()
// ... specific mappings
.ReverseMap();
config.CreateMap<B, BViewModel>()
.MapToBaseViewModel()
// ... specific mappings
.ReverseMap();
Update: It turns out that automatic reverse mapping in the latest at this time AutoMapper 6.1.1 works for the lambda overload of MapFrom, but not for the string overload (in AutoMapper 5 it doesn't work at all). So until it gets fixed, you can use the following MapFrom(string) replacement:
public static class AMExtensions
{
public static void From<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt, string name)
{
var parameter = Expression.Parameter(typeof(TSource), "src");
var body = Expression.PropertyOrField(parameter, name);
var selector = Expression.Lambda(body, parameter);
opt.MapFrom((dynamic)selector);
}
}
Which means you'll need to replace MapFrom calls in the original solution with From, because we can't give the extension method the same name since it has less priority than the concrete interface method.
Probably too much effort compared to base class approach. But useful in case you can't control the design of the entity classes.
You need a base class for the source classes. You create a map between the base source class and the destination class. You include that map in the map for the derived classes. That would allow you to reuse the configuration. The docs. For simple cases you can use As instead of Include.
You set an attribute on properties on your models.
This contains the name of the property it maps from on a source object.
Then you make a generic method that accepts the target object and source object, that finds the customattribute on the property where the attribute was set, and the property on the target object (or vice versa) and then sets the value.
You can even handle nested objects by asking if its a class property or not.

Automapper 5.1.1 inheritance mapping

Hello I cant get my mappings working for inherited class.
Idea is to create map for base object and interface only once, and when child classes implements their own members, configure mapping nly for those members that are not defined in base class or intrface.
Let me begin with sample code.
public class DtoClass {
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
public interface IField3 {
public string EntityField3 { get; set; }
}
public class BaseEntityClass {
public string EntityField1 { get; set; }
}
public class ChildEntityClass : BaseEntityClass, IField3 {
public string EntityField2 { get; set; }
public string EntityField3 { get; set; }
}
CreateMap<BaseEntityClass, DtoClass>()
.ForMember(c => c.Field1 , m => m.MapFrom(a => a.EntityField1))
.Include<ChildEntityClass, DtoClass>();
CreateMap<IField3, DtoClass>()
.ForMember(c => c.Field3 , m => m.MapFrom(a => a.EntityField3));
CreateMap<ChildEntityClass, DtoClass>()
.ForMember(c => c.Field2 , m => m.MapFrom(a => a.EntityField2));
Attached code dosnt work ofcourse. when calling :
AutoMapper.Mapper.Map<ChildEntityClass, DtoClass>(instanceOfChildEntityClass);
I get only mapped members that are defined in CreateMap<ChildEntityClass, DtoClass>().
Any idea how to implement mappings for base class and interfaces only once?
And yes i want to map all types ChildEntityClass, BaseEntityClass and IField3 to DtoClass.
Any hints are welcome for elegant configuration such mappings.
Edit: I remove unecssary IncludeBase from subclass for clarity, but none of both
- IncludeBase in subclass
- Include in base class
Works for me. What can cause such problems ?
You should not be using .IncludeBase AND .Include - pick one and stick with it. I prefer .IncludeBase, as I think it makes more sense to define in the subclass. In your case, you cannot reference IField3 using Include because there is no implicit conversion.
The following code works using IncludeBase for me
CreateMap<BaseEntityClass, DtoClass>()
.ForMember(dest => dest.Field1, opt => opt.MapFrom(src => src.EntityField1))
;
CreateMap<IField3, DtoClass>()
.ForMember(dest => dest.Field3, opt => opt.MapFrom(src => src.EntityField3))
;
CreateMap<ChildEntityClass, DtoClass>()
.ForMember(dest => dest.Field2, opt => opt.MapFrom(src => src.EntityField2))
.IncludeBase<BaseEntityClass, DtoClass>()
.IncludeBase<IField3, DtoClass>()
;
As it often happens issue were outside the scope i delivered in sample code.
In my project in initialization method was hidden invocation of something like:
foreach (string propName in map.GetUnmappedPropertyNames())
{
expr.ForMember(propName, opt => opt.Ignore());
}
So all columns not mapped in subclass were automatically ignored even when invoking mapping for base type. Simple yet problematic.
Such code as above were probably aded for Mapper.Configuration.AssertConfigurationIsValid(); to pass.

AutoMapper use parent class mapping in child classes

I've got two parent classes
class Settings
{
public Field
}
and
class SettingsDb
{
public FieldDB
}
and Field mapped width FieldDB by ForMember expression
CreateMap<SettingsDb, Settings>()
.ForMember(dest => dest.Field,
opt => opt.MapFrom(src => some lambda with src.FieldDb));
I also have two child classes
class AdminSettings : Setting
{
inherits Field
}
class AdminSettingsDb : SettingDb
{
inherits FieldDb
}
how can I map AdminSettings with AdminSettingsDb without doing ForMember again for this inherited fields?
CreateMap<AdminSettings, AdminSettingsDb>();
now this fields are empty after mapping
Simpler solution
CreateMap<BaseEntity, BaseDto>()
.ForMember(dest => dest.SomeMember, opt => opt.MapFrom(src => src.OtherMember));
CreateMap<DerivedEntity, DerivedDto>()
.IncludeBase<BaseEntity, BaseDto>();
Include the child classes in the mapping of the parent.
CreateMap<SettingsDb, Settings>()
.Include<AdminSettings, AdminSettingsDb>()
.ForMember(dest => dest.Field,
opt => opt.MapFrom(src => some lambda with src.FieldDb));
CreateMap<AdminSettings, AdminSettingsDb>();
See Mapping inheritance.

Override resolved property type with AutoMapper

With AutoMapper, can I override the resolved type for a property? For example, given these classes:
public class Parent
{
public Child Value { get; set; }
}
public class Child { ... }
public class DerivedChild : Child { ... }
Can I configure AutoMapper to automap the Child Value property with a DerivedChild instance? The hypothetical mapping would look something like this:
map.CreateMap<ChildEntity, DerivedChild>();
map.CreateMap<ParentEntity, Parent>()
.ForMember(p => p.Value, p => p.UseDestinationType<DerivedChild>());
(I'm projecting from LINQ entities. The closest I could find is using a custom type converter, but it looks like I'd need to override the entire mapping.)
Here is one way to do it:
map.CreateMap<ChildEntity, DerivedChild>();
map.CreateMap<ParentEntity, Parent>()
.ForMember(
x => x.Value,
opt => opt.ResolveUsing(
rr => map.Map<DerivedChild>(((ParentEntity)rr.Context.SourceValue).Value)));
ResolveUsing allows to specify custom logic for mapping the value.
The custom logic that is used here is actually calling map.Map to map to DerivedChild.
You may re-assign new object manually after mapping:
Mapper.CreateMap<ParentEntity, Parent>()
.AfterMap((src, dest) => dest.Value = new DerivedChild(){...});
or even re-map it:
.AfterMap((src, dest) => dest.Value = Mapper.Map<DerivedChild>(dest.Value));

Categories