I have a mapping definition defined as
Mapper.CreateMap<Calculator, CalculatorViewModel>()
.ForMember(dest => dest.TypeIndicator, src => src.ResolveUsing(new TypeIndicatorResolver()));
Should I be using ResolveUsing or MapFrom(src => SomePrivateMethod()) ?
What is the difference between ResolveUsing and MapFrom when it comes to complex mapping.
The Resolver or Private method will go to the database and get a value.
MapFrom uses Expressions, while ResolveUsing uses a Func. MapFrom only allows redirection of properties:
ForMember(d => d.Foo, opt => opt.MapFrom(src => src.Bar.Baz.Foo))
ResolveUsing can be anything
ForMember(d => d.Foo, opt => opt.ResolveUsing(src => HitDatabaseWithStuff(src));
I'd use a Resolver class when the resolution logic needs to be shared amongst more than one member, or if I want to have the resolver instantiated by a service locator. Otherwise, a private method is fine.
Related
I want to upgrade AutoMapper library in my project from v4.2.1 to the latest that still supports .NET Framework 4.5.2 (which to my knowledge is AutoMapper 7.0.1). I have code like this:
Mapper.Initialize(cfg =>
{
cfg.AllowNullCollections = true;
cfg.CreateMap<ExampleDto, Example>(MemberList.Source)
.ForMember(dest => dest.ItemsAAA, o => o.MapFrom(x => x.items.ConvertTo<TypeAAA>()))
.ForMember(dest => dest.ItemsBBB, o => o.MapFrom(x => x.items.ConvertTo<TypeBBB>()));
});
ExampleDto.items is a collection that should be split to Example.ItemsAAA and Example.ItemsBBB based on every item type. ConvertTo is an extension method, and it returns an empty collection if input parameter is null. This used to work in AutoMapper 4.2.1, but in 7.0.1 ConvertTo is never called for x.items == null. How can I make AutoMapper call this mapping even if the x.items is null?
I've configured AutoMapper to, I thought, use AutoFac to create object instances by defining the following AutoFac Module:
public class AutoMapperModule : Module
{
protected override void Load( ContainerBuilder builder )
{
base.Load( builder );
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
builder.RegisterAssemblyTypes( assemblies )
.Where( t => typeof(Profile).IsAssignableFrom( t ) && !t.IsAbstract && t.IsPublic )
.As<Profile>();
builder.Register( c => new MapperConfiguration( cfg =>
{
//cfg.ConstructServicesUsing( CSContainer.Instance.Resolve );
foreach( var profile in c.Resolve<IEnumerable<Profile>>() )
{
cfg.AddProfile( profile );
}
} ) )
.AsSelf()
.AutoActivate()
.SingleInstance();
builder.Register( c => c.Resolve<MapperConfiguration>().CreateMapper( c.Resolve ) )
.As<IMapper>()
.SingleInstance();
}
}
I then created AutoMapper Profiles such as the following:
public class CommunityScannerManagerAutoMapProfile : Profile
{
public CommunityScannerManagerAutoMapProfile()
{
CreateMap<CommunityUser, CommunityUserModel>()
//.ConstructUsing(src => new CommunityUserModel(CSContainer.Instance.Resolve<IValidationService>()))
.ForMember( dest => dest.VaultKey, opt => opt.MapFrom( src => src.VaultKeyName ) )
.ReverseMap();
CreateMap<Community, CommunityModel>()
//.ConstructUsing(src=>new CommunityModel(CSContainer.Instance.Resolve<IValidationService>( )))
.ReverseMap();
CreateMap<ScannerConfiguration, CommunitiesModel>()
.ForMember( dest => dest.Communities, opt => opt.MapFrom( src => src.Communities ) )
.ReverseMap();
}
}
The destination types form a hierarchy: CommunitiesModel contains a collection of CommunityModel objects, and each CommunityModel object contains a collection of CommunityUserModel objects. The collections are mapped thru the AutoMapper Profile I created.
Each destination type -- CommunitiesModel, CommunityModel, and CommunityUserModel -- has constructor parameters which are registered with AutoFac (they themselves are also registered with AutoFac). So I thought AutoMapper would be able to resolve the instances it needed, calling upon AutoFac to create them.
But that's not the case. Unless I include those two commented out lines in the AutoMapper Profile class -- the ones starting with ConstructUsing -- I get an exception, from AutoMapper, that it can only handle classes with constructors that have no parameters, or only optional ones. Which implies AutoMapper is not using AutoFac to resolve/create those instances, contrary to what I expected.
Interestingly, AutoMapper has no problem creating instances of CommunitiesModel, the top level object, even though it, too, only has a constructor containing a parameter. So AutoMapper is using AutoFac for top level objects, but not the child objects.
Is there a global configuration I can set in AutoMapper to tell it to always use AutoFac? I tried using ConstructServicesUsing in configuring AutoMapper (the line is commented out in the code here), but it didn't seem to have any effect.
Update
Apparently, AutoMapper doesn't use DI to create objects "internally" (see #Lucian's comment).
But I noticed AutoMapper does support a ConstructUsing() clause, which allows you to tie it into a DI container like AutoMap:
CreateMap<CommunityUser, CommunityUserModel>()
.ConstructUsing(src => CSContainer.Instance.Resolve<CommunityUserModel>())
.ForMember( dest => dest.VaultKey, opt => opt.MapFrom( src => src.VaultKeyName ) )
.ReverseMap();
So a related/follow-on question is: is there a way to make this the behavior without having to add the line to every CreateMapper() call where it applies?
Most of my EF objects have a TenantId Property. The system never have to handle the tenantId it's all taken care for before the SaveChanges(). I want to write an automapper map that will always ignore the TenantId Field.
I've tried
Mapper.CreateMap<IDomainObject, ITenantData>()
.ForMember(m => m.TenantId, a => a.Ignore());
You can handle it using mapping inheritance. For instance:
Mapper.CreateMap<IDomainObject, ITenantData>()
.Include<DomainObject1, TenantData1>()
.ForMember(m => m.TenantId, a => a.Ignore());
I tried the following map:
CreateMap<ThemeNewModel, CreateThemeOrder.ThemeModel>()
.ForMember(d => d.Subject.Id, o => o.MapFrom(s => s.Subject));
Both Subject.Id and Subject are of type int. However, I get the following error:
Expression 'd => Convert(d.Subject.Id)' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.Parameter name: lambdaExpression
I am using AutoMapper 2.0. Can't I solve this without AfterMap?
What is the type of ThemeNewModel.Subject? Assuming its ThemeSubject, you may have success with something like:
CreateMap<ThemeSubject, CreateThemeOrder.ThemeModel>()
.ForMember(d=>d.Id, o => o.MapFrom(s->s.Subject));
CreateMap<ThemeNewModel, CreateThemeOrder.ThemeModel>()
.ForMember(d=>d.Subject, o => o.MapFrom(s => s);
If the above does not work, you should follow the advise in the exception, and create a custom resolver.
Anyway, automapper is designed to flatten from complex types to a flat model/viewmodel types, so your ThemeNewModel is too complex, and maybe you need to rethink your design.
Is it possible to use AutoMapper to map from Source to Destination conditionally resolving some properties based on the property value of another object? For example, mapping Source.Property to Destination.Property where ThirdObject.CountryCode.Equals("SomeCountry").
The current code base is setup so that values are being mapped from a DataReader to a list of objects. Then, if the ThirdObject.CountryCode has a certain value, then an amount property on the destination object must be multiplied by a multiplier.
Currently, I'm thinking of solving the problem by coming up with something like:
Mapper.Map<IDataReader, Destination>(dataReader)
.OnCondition(ThirdObject.CountryCode.Equals("SomeCountry")
.ForMember(destination => destination.Amount)
.UpdateUsing(new Multiplier(fixedAmount));
I'm hoping there is an easier way before going down that path.
Look at ResolveUsing:
Mapper.CreateMap<Journal_Table, Journal>()
.ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.JournalId))
.ForMember(dto => dto.Level, opt => opt.ResolveUsing<JournalLevelResolver>().FromMember(name => name.Journal_level));
Then:
public class JournalLevelResolver : ValueResolver<string, JournalLevel>
{
protected override JournalLevel ResolveCore(string level)
{
...