[英]Automapper 5.2 ignores ExplicitExpansion if it is configured in Base DTO mapping
如果在基本數據傳輸對象的映射中配置了自動映射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
我錯過了一些代碼嗎?
你沒有錯過任何東西。
或者這是Automapper中的錯誤,我必須向Automapper的錯誤跟蹤器報告此問題? 或者它可能是“按設計”,但為什么呢?
我懷疑它是“按設計”,很可能是bug或不完整的快速和臟實現。 可以在PropertyMap
類的ApplyInheritedPropertyMap
方法的源代碼中看到它,該方法負責組合基本屬性和派生屬性配置。 “繼承的”映射屬性目前是:
CustomExpression
CustomResolver
Condition
PreCondition
NullSubstitute
MappingOrder
ValueResolverConfig
而以下(基本上所有bool
類型)屬性(包括有問題的屬性)不是:
AllowNull
UseDestinationValue
ExplicitExpansion
IMO的問題是當前的實現無法確定是否明確設置了bool
屬性。 當然,通過用顯式bool?
替換auto屬性可以很容易地解決它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
實例比較來過濾投影。
第一種情況(帶表達式)對於值類型失敗,因為嘗試從Expression<Func<T, object>>
提取MemberInfo
的實現只需要MemberExpression
,但是在值類型的情況下,它包含在Expression.Convert
。
第二種情況(帶有屬性名稱)失敗,因為它們沒有考慮這樣的事實:從編譯時 lambda表達式中提取的基類繼承的屬性的 MemberInfo
與反射或運行時創建的表達式檢索的屬性不同,這與以下測試:
// From reflection
var nameA = typeof(DtoDerived).GetMember(nameof(DtoDerived.Name)).Single();
// Same as
//var nameA = typeof(DtoDerived).GetProperty(nameof(DtoDerived.Name));
// From compile time expression
Expression<Func<DtoDerived, NameDtoType>> compileTimeExpr = _ => _.Name;
var nameB = ((MemberExpression)compileTimeExpr.Body).Member;
// From runtime expression
var runTimeExpr = Expression.PropertyOrField(Expression.Parameter(typeof(DtoDerived)), nameof(DtoDerived.Name));
var nameC = runTimeExpr.Member;
Assert.AreEqual(nameA, nameC); // Success
Assert.AreEqual(nameA, nameB); // Fail
你肯定需要報告這兩個問題。 我會說,在提供表達式列表時,任何值類型屬性都會泄露該功能,而在提供名稱時,任何繼承的屬性都會泄露。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.