AutoMapper: What is the difference between MapFrom

2019-01-09 05:03发布

问题:

Ignoring the ResolveUsing overloads that take an IValueResolver, and looking only at these 2 methods:

void ResolveUsing(Func<TSource, object> resolver);
void MapFrom<TMember>(Expression<Func<TSource, TMember>> sourceMember);

The main difference between these 2 seems to be that ResolveUsing takes a Func<TSource, object>, whereas MapFrom takes an Expression<Func<TSource, TMember>>.

However in client code that actually uses one of these methods with a lambda expression, they seem to be interchangeable:

Mapper.CreateMap<SourceType, DestType>() // uses ResolveUsing
   .ForMember(d => d.DestPropX, o => o.ResolveUsing(s => s.SourcePropY));

Mapper.CreateMap<SourceType, DestType>() // uses MapFrom
   .ForMember(d => d.DestPropX, o => o.MapFrom(s => s.SourcePropY));

So what ultimately is the difference between the above 2 choices? Is one faster than the other? Is one a better choice than the other and if so, when / why?

回答1:

In the past I had a long email exchange on the mailing list with the author of Automapper. MapFrom will do null checks all the way trough the expression:

So you can do opt => opt.MapFrom(src => src.SomeProp.Way.Down.Here.Somewhere) and each level will get checked for nulls (as it already does for flattening).



回答2:

I just did some benchmarks using the new C# 6 null conditional operator ?.

Consider the following scenario: class A has a child class B, which has a child C, whose Name property we want to flatten into a DTO. I tested two variants:

// using mapfrom
CreateMap<MapFromA, MapFromADto>()
    .ForMember(dto => dto.Name, o => o.MapFrom(a => a.B.C.Name));

// using resolveusing with elvis
CreateMap<ResolveUsingX, ResolveUsingXDto>()
    .ForMember(dto => dto.Name, o => o.ResolveUsing(x => x.Y?.Z?.Name));

I called _mapper.Map<ResolveUsingXDto>(x); or _mapper.Map<MapFromADto>(a); for 1000 different ResolveUsingX x and MapFromA a and took the time using a System.Diagnostics.StopWatch. Here are my results:

Distinct elements per batch: 1000; # batches for average: 25

A->B->C.Name, C is never null.
MapForm - average time taken for 1000x: 5527,84 ticks = 1,44 ms.
ResolveUsing - average time taken for 1000x: 5479,76 ticks =  1,4 ms.

A->B->C.Name, C is null 1/3 of the time.
MapForm - average time taken for 1000x: 72924,4 ticks = 27,44 ms.
ResolveUsing - average time taken for 1000x: 5351,2 ticks =  1,48 ms.

A->B->C.Name, C is null 1/2 of the time.
MapForm - average time taken for 1000x: 107016,92 ticks = 40,52 ms.
ResolveUsing - average time taken for 1000x: 5835,32 ticks =  1,56 ms.

A->B->C.Name, C is null 2/3 of the time.
MapForm - average time taken for 1000x: 141437,96 ticks = 53,64 ms.
ResolveUsing - average time taken for 1000x: 5789,72 ticks =  1,56 ms.

MapFrom has to catch NullReferenceExceptions, which is slower than ResolveUsing with the elvis operator ?.



回答3:

MapFrom has a few extra smarts. For example (from the mailing list):

In MapFrom, I try to be smart about digging in to child properties (much like the normal flattening does). MapFrom is an attempt to mimic flattening, with an added bit of allowing redirection. ResolveUsing doesn't have this behavior.

I'm not sure if this is fully documented anywhere (apart from in the source code).



回答4:

Although in many situations either can be used, based on official documentation there is a difference when it comes to LINQ projections. Detailed explanation can be found here.

Long story short: use MapFrom whenever possible.



回答5:

According to the source code, ResolveUsing is more complicated. The source value can be any object; therefore, you can use any value you want to fill the destination member, such as int or bool that you get by "Resolving" the given object. However, MapFrom only uses member to map from.

/// <summary>
/// Resolve destination member using a custom value resolver callback. Used instead of MapFrom when not simply redirecting a source member
/// This method cannot be used in conjunction with LINQ query projection
/// </summary>
/// <param name="resolver">Callback function to resolve against source type</param>
void ResolveUsing(Func<TSource, object> resolver);

/// <summary>
/// Specify the source member to map from. Can only reference a member on the <typeparamref name="TSource"/> type
/// This method can be used in mapping to LINQ query projections, while ResolveUsing cannot.
/// Any null reference exceptions in this expression will be ignored (similar to flattening behavior)
/// </summary>
/// <typeparam name="TMember">Member type of the source member to use</typeparam>
/// <param name="sourceMember">Expression referencing the source member to map against</param>
void MapFrom<TMember>(Expression<Func<TSource, TMember>> sourceMember);