Подтвердить что ты не робот

AutoMapper: В чем разница между MapFrom и ResolveUsing?

Игнорирование перегрузок ResolveUsing, которые принимают IValueResolver, и глядя только на эти 2 метода:

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

Основное различие между этими двумя кажется, что ResolveUsing принимает значение Func<TSource, object>, тогда как MapFrom принимает Expression<Func<TSource, TMember>>.

Однако в клиентском коде, который фактически использует один из этих методов с лямбда-выражением, они кажутся взаимозаменяемыми:

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

Итак, в чем же разница между двумя вышеуказанными вариантами? Быстрее, чем другой? Является ли лучший выбор, чем другой, и если да, то когда/почему?

4b9b3361

Ответ 1

В прошлом у меня был длинный обмен сообщениями в списке рассылки с автором Automapper. MapFrom будет выполнять нулевые проверки полностью через выражение:

Итак, вы можете сделать opt => opt.MapFrom(src => src.SomeProp.Way.Down.Here.Somewhere), и каждый уровень будет проверен для нулей (как это уже делается для выравнивания).

Ответ 2

Я только что сделал некоторые тесты, используя новый оператор С# 6 null условный ?.

Рассмотрим следующий сценарий: class A имеет дочерний класс B, который имеет дочерний элемент C, свойство Name, которое мы хотим сгладить в DTO. Я протестировал два варианта:

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

Я назвал _mapper.Map<ResolveUsingXDto>(x); или _mapper.Map<MapFromADto>(a); для 1000 различных ResolveUsingX x и MapFromA a и занял время, используя System.Diagnostics.StopWatch. Вот мои результаты:

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 должен поймать NullReferenceExceptions, который медленнее, чем ResolveUsing с оператором elvis ?.

Ответ 3

MapFrom имеет несколько дополнительных умений. Например (из список рассылки):

В MapFrom я стараюсь быть умным в том, чтобы вникать в дочерние свойства (как это делает нормальное сглаживание). MapFrom - это попытка подражать сглаживанию с добавлением бит разрешения перенаправления. ResolveUsing не имеет такого поведения.

Я не уверен, что это полностью документировано в любом месте (кроме исходный код).

Ответ 4

В соответствии с исходным кодом ResolveUsing сложнее. Исходным значением может быть любой объект; поэтому вы можете использовать любое значение, которое вы хотите заполнить целевым элементом, например int или bool, которое вы получаете посредством "Разрешения" данного объекта. Однако MapFrom использует только член для сопоставления.

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