如果在基本数据传输对象的映射中配置了自动映射5.2(目前最新),则忽略ExplicitExpansion()配置。但是,如果直接在Derived DTO中配置了映射,它仍然可以正常工作。我有一对DTO类,它们在字段集和映射配置中包含了很多重复,我试图将它隔离到常见的基本DTO类,但是这个问题阻止了我这样做。
以下是说明这种奇怪行为的代码。有四个测试,其中两个失败,断言没有扩展基础DTO的属性。如果我将1-1..1-4行移到2.1位置,所有测试都会通过。
我是否错过了一些代码或者这是Automapper中的错误,我必须向Automapper的错误跟踪器报告此问题?或者它可能是“按设计”,但为什么呢? (Ivan Stoev提出了一个有效的解决办法,但请允许我推迟接受答案,因为我面临的问题不是那么简单,我在下面的更新中添加了更多详细信息。)
的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 即可。 Ivan Stoev全面回答了如何解决上面编码的问题。除非我被迫使用字段名称的字符串数组而不是MemberExpressions,否则它的效果非常好。这与这种方法与Value类型的成员(例如int,int?)崩溃有关。它在下面的第一个单元测试中以及崩溃堆栈跟踪中进行了演示。我会在另一个问题中询问它,或者更确切地说是在bug跟踪器中创建一个问题,因为崩溃肯定是一个bug。
UnitTest2.cs - 修复了Ivan 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
答案 0 :(得分:5)
我错过了一些代码
你没有错过任何东西。
或者这是Automapper中的错误,我必须将此问题报告给Automapper的错误跟踪器?或者它可能&#34;设计&#34;,但为什么?
我怀疑它&#34;设计&#34;,很可能是错误或不完整的快速和脏实施。可以在ApplyInheritedPropertyMap
类的PropertyMap
方法的source code内查看,该方法负责组合基本属性和派生属性配置。 &#34;继承&#34;目前的映射属性是:
CustomExpression
CustomResolver
Condition
PreCondition
NullSubstitute
MappingOrder
ValueResolverConfig
而以下(基本上所有bool
类型)属性(包括有问题的属性)不是:
AllowNull
UseDestinationValue
ExplicitExpansion
问题IMO是当前实现无法确定是否明确设置了bool
属性。当然,可以通过使用显式bool?
支持字段和默认值逻辑替换自动属性(以及在基类配置中打开它时关闭它的其他流畅配置方法)来轻松解决此问题。 。不幸的是,它只能在源代码中完成,因此我建议您将问题报告给他们的问题跟踪器。
直到(以及如果)他们修复它,我可以建议将所有常用代码移动到自定义扩展方法,如
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();
});
}
}
并从主配置代码中使用它们:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, DtoBase>()
.Configure();
cfg.CreateMap<Source, DtoDerived>()
.Configure()
.ForMember(dto => dto.Desc, conf => {
conf.MapFrom(src => src.Desc);
conf.ExplicitExpansion();
});
});
修改:关于其他问题。两者都是更严重的AM处理错误,与配置无关。
问题在于他们尝试使用MemberInfo
实例比较来过滤投影。
第一种情况(带有表达式)对于值类型失败,因为尝试从MemberInfo
中提取Expression<Func<T, object>>
的实现只需要MemberExpression
,但在值类型的情况下,它是&#39;包裹在Expression.Convert
内。
第二种情况(带有属性名称)失败,因为他们没有考虑{<1}}从编译时提取的属性继承自基类这一事实 lambda表达式与反射或运行时创建的表达式检索的表达式不同,通过以下测试证明:
MemberInfo
您肯定需要报告这两个问题。我会说在提供表达式列表时,任何值类型属性都会泄露该功能,并且在提供名称时,对于任何继承的属性,该功能都会受到影响。