Automapper 5.2 (последний на данный момент) игнорирует конфигурацию ExplicitExpansion(), если она настроена при сопоставлении базового объекта передачи данных. Но он по-прежнему работает правильно, если отображение настроено непосредственно в Derived DTO. У меня есть пара классов DTO, которые содержат так много дубликатов в настройках полей и сопоставления, которые я пытаюсь изолировать в общем базовом классе DTO, но эта проблема мешает мне это делать.Automapper 5.2 игнорирует ExplicitExpansion, если он настроен в сопоставлении базового DTO
Ниже приведен код, иллюстрирующий это странное поведение. Есть четыре теста, два из которых не могут утверждать не расширенное свойство базы DTO. Если я переместил строки 1-1..1-4 на место 2.1, все тесты пройдут.
Я пропустил какой-то фрагмент кода или это ошибка в Automapper, и я должен сообщить об этой проблеме автозагрузчику Automapper? Или это возможно «по дизайну», но почему? (Иван Стоев предложил исправление, но позвольте мне отложить принятие ответа, потому что проблема, с которой я сталкиваюсь, не так проста, и я добавил более подробные сведения об обновлении ниже).
UnitTest1.cs:
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AutoMapperIssue
{
public class Source { public string Name; public string Desc; }
public class DtoBase { public string Name { get; set; } }
public class DtoDerived : DtoBase { public string Desc { get; set; } }
[TestClass] public class UnitTest1
{
[AssemblyInitialize] public static void AssemblyInit(TestContext context)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, DtoBase>()
.ForMember(dto => dto.Name, conf => { // line 1-1
conf.MapFrom(src => src.Name); // line 1-2
conf.ExplicitExpansion(); // line 1-3
}) // line 1-4
.Include<Source, DtoDerived>();
cfg.CreateMap<Source, DtoDerived>()
// place 2.1
.ForMember(dto => dto.Desc, conf => {
conf.MapFrom(src => src.Desc);
conf.ExplicitExpansion();
});
});
Mapper.Configuration.CompileMappings();
Mapper.AssertConfigurationIsValid();
}
private readonly IQueryable<Source> _iq = new List<Source> {
new Source() { Name = "Name1", Desc = "Descr",},
} .AsQueryable();
[TestMethod] public void ProjectAll_Success()
{
var projectTo = _iq.ProjectTo<DtoDerived>(_ => _.Name, _ => _.Desc);
Assert.AreEqual(1, projectTo.Count()); var first = projectTo.First();
Assert.IsNotNull(first.Desc); Assert.AreEqual("Descr", first.Desc);
Assert.IsNotNull(first.Name); Assert.AreEqual("Name1", first.Name);
}
[TestMethod] public void SkipDerived_Success()
{
var projectTo = _iq.ProjectTo<DtoDerived>(_ => _.Name);
Assert.AreEqual(1, projectTo.Count()); var first = projectTo.First();
Assert.IsNotNull(first.Name); Assert.AreEqual("Name1", first.Name);
Assert.IsNull(first.Desc, "Should not be expanded.");
}
[TestMethod] public void SkipBase_Fail()
{
var projectTo = _iq.ProjectTo<DtoDerived>(_ => _.Desc);
Assert.AreEqual(1, projectTo.Count()); var first = projectTo.First();
Assert.IsNotNull(first.Desc); Assert.AreEqual("Descr", first.Desc);
Assert.IsNull(first.Name, "Should not be expanded. Fails here. Why?");
}
[TestMethod] public void SkipAll_Fail()
{
var projectTo = _iq.ProjectTo<DtoDerived>();
Assert.AreEqual(1, projectTo.Count()); var first = projectTo.First();
Assert.IsNull(first.Desc, "Should not be expanded.");
Assert.IsNull(first.Name, "Should not be expanded. Fails here. Why?");
}
}
}
packages.config:
<package id="AutoMapper" version="5.2.0" targetFramework="net452" />
UPD. Иван Стоев всесторонне ответил, как исправить проблему, закодированную выше. Он работает очень хорошо, если я не вынужден использовать строковые массивы имен полей вместо MemberExpressions. Это связано с тем, что этот подход падает с членами типа Value (например, int, int?). Это продемонстрировано в первом блочном тесте ниже вместе с трассировкой стека аварий. Я спрошу об этом в другом вопросе или, скорее, создаст проблему в трекере с ошибкой, так как крах определенно является ошибкой.
UnitTest2.cs - с исправлением от ответа Ивана Stoev в
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AutoMapperIssue.StringPropertyNames
{ /* int? (or any ValueType) instead of string - .ProjectTo<> crashes on using MemberExpressions in projction */
using NameSourceType = Nullable<int> /* String */; using NameDtoType = Nullable<int> /* String */;
using DescSourceType = Nullable<int> /* String */; using DescDtoType = Nullable<int> /* String*/;
public class Source
{
public NameSourceType Name { get; set; }
public DescSourceType Desc { get; set; }
}
public class DtoBase { public NameDtoType Name { get; set; } }
public class DtoDerived : DtoBase { public DescDtoType Desc { get; set; } }
static class MyMappers
{
public static IMappingExpression<TSource, TDestination> Configure<TSource, TDestination>(this IMappingExpression<TSource, TDestination> target)
where TSource : Source
where TDestination : DtoBase
{
return target.ForMember(dto => dto.Name, conf =>
{
conf.MapFrom(src => src.Name);
conf.ExplicitExpansion();
});
}
}
[TestClass] public class UnitTest2
{
[ClassInitialize] public static void ClassInit(TestContext context)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, DtoBase>()
.Configure()
.Include<Source, DtoDerived>();
cfg.CreateMap<Source, DtoDerived>()
.Configure()
.ForMember(dto => dto.Desc, conf => {
conf.MapFrom(src => src.Desc);
conf.ExplicitExpansion();
})
;
});
Mapper.Configuration.CompileMappings();
Mapper.AssertConfigurationIsValid();
}
private static readonly IQueryable<Source> _iq = new List<Source> {
new Source() { Name = -25 /* "Name1" */, Desc = -12 /* "Descr" */, },
} .AsQueryable();
private static readonly Source _iqf = _iq.First();
[TestMethod] public void ProjectAllWithMemberExpression_Exception()
{
_iq.ProjectTo<DtoDerived>(_ => _.Name, _ => _.Desc); // Exception here, no way to use Expressions with current release
//Test method AutoMapperIssue.StringPropertyNames.UnitTest2.ProjectAllWithMemberExpression_Exception threw exception:
//System.NullReferenceException: Object reference not set to an instance of an object.
//
// at System.Linq.Enumerable.<SelectManyIterator>d__16`2.MoveNext()
// at System.Linq.Enumerable.<DistinctIterator>d__63`1.MoveNext()
// at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
// at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
// at AutoMapper.QueryableExtensions.ProjectionExpression.To[TResult](IDictionary`2 parameters, IEnumerable`1 memberPathsToExpand)
// at AutoMapper.QueryableExtensions.ProjectionExpression.To[TResult](Object parameters, Expression`1[] membersToExpand)
// at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Object parameters, Expression`1[] membersToExpand)
// at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, Expression`1[] membersToExpand)
// at AutoMapperIssue.StringPropertyNames.UnitTest2.ProjectAllWithMemberExpression_Exception() in D:\01\AutoMapperIssue\UnitTest2.cs:line 84
}
#pragma warning disable 649
private DtoDerived d;
#pragma warning restore 649
[TestMethod] public void ProjectAll_Fail()
{
var projectTo = _iq.ProjectTo<DtoDerived>(null, new string[] { nameof(d.Name), nameof(d.Desc) } /* _ => _.Name, _ => _.Desc */);
Assert.AreEqual(1, projectTo.Count()); var first = projectTo.First();
Assert.IsNotNull(first.Desc, "Should be expanded."); Assert.AreEqual(_iqf.Desc, first.Desc);
Assert.IsNotNull(first.Name, "Should be expanded. Fails here, why?"); Assert.AreEqual(_iqf.Name, first.Name);
}
[TestMethod] public void BaseOnly_Fail()
{
var projectTo = _iq.ProjectTo<DtoDerived>(null, new string[] { nameof(d.Name) } /* _ => _.Name */);
Assert.AreEqual(1, projectTo.Count()); var first = projectTo.First();
Assert.IsNull(first.Desc, "Should NOT be expanded.");
Assert.IsNotNull(first.Name, "Should be expanded. Fails here, why?"); Assert.AreEqual(_iqf.Name, first.Name);
}
[TestMethod] public void DerivedOnly_Success()
{
var projectTo = _iq.ProjectTo<DtoDerived>(null, new string[] { nameof(d.Desc) } /* _ => _.Desc */);
Assert.AreEqual(1, projectTo.Count()); var first = projectTo.First();
Assert.IsNotNull(first.Desc, "Should be expanded."); Assert.AreEqual(_iqf.Desc, first.Desc);
Assert.IsNull(first.Name, "Should NOT be expanded.");
}
[TestMethod] public void SkipAll_Success()
{
var projectTo = _iq.ProjectTo<DtoDerived>(null, new string[] { });
Assert.AreEqual(1, projectTo.Count()); var first = projectTo.First();
Assert.IsNull(first.Desc, "Should NOT be expanded.");
Assert.IsNull(first.Name, "Should NOT be expanded.");
}
}
}
UPD2. Обновленный вопрос выше определенно не может быть установлен за пределами, см. Комментарий в соответствии с принятым ответом. Это проблема самого AutoMapper. Если вы не можете ждать, чтобы исправить обновленный вопрос, вы можете сделать свой патч AutoMapper используя следующие простые (но не незначительные) различия: https://github.com/moudrick/AutoMapper/commit/65005429609bb568a9373d7f3ae0a535833a1729
Иван, спасибо за всесторонний ответ! Я готов принять ваш ответ незадолго до окончания периода выплаты, если никто не ответил на обновленную проблему, но позвольте мне оставить щедрость открытой, надеясь получить разрешение на фактическую проблему, с которой я столкнулся. Кроме того, не могли бы вы ответить на обновленную проблему? – moudrick
Конечно, я посмотрю. –
@moudrick К сожалению, дополнительные проблемы не могут быть решены извне. Фактически, выражение с выражениями может быть легко разрешено, но для этого не требуется использовать другое имя метода, чем 'ProjectTo', чтобы не столкнуться с AM' QueryableExtensions'. Дайте знать, если вас это заинтересовало. Печально то, что на уровне исходного кода исправление довольно простое. –