繁体   English   中英

如果在Base DTO映射中配置了ExplicitExpansion,Automapper 5.2将忽略它

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM