【问题标题】:Automapper - how to map to constructor parameters instead of property settersAutomapper - 如何映射到构造函数参数而不是属性设置器
【发布时间】:2011-01-15 09:33:57
【问题描述】:

如果我的目标设置器是私有的,我可能想使用目标对象的构造函数映射到该对象。你会如何使用 Automapper 来做到这一点?

【问题讨论】:

    标签: constructor automapper


    【解决方案1】:

    使用ConstructUsing

    这将允许您指定在映射期间使用哪个构造函数。但随后所有其他属性将根据约定自动映射。

    另请注意,这与 ConvertUsing 不同,convert using 不会继续通过约定映射,而是让您完全控制映射。

    Mapper.CreateMap<ObjectFrom, ObjectTo>()
        .ConstructUsing(x => new ObjectTo(arg0, arg1, etc));
    

    ...

    using AutoMapper;
    using NUnit.Framework;
    
    namespace UnitTests
    {
        [TestFixture]
        public class Tester
        {
            [Test]
            public void Test_ConstructUsing()
            {
                Mapper.CreateMap<ObjectFrom, ObjectTo>()
                    .ConstructUsing(x => new ObjectTo(x.Name));
    
                var from = new ObjectFrom { Name = "Jon", Age = 25 };
    
                ObjectTo to = Mapper.Map<ObjectFrom, ObjectTo>(from);
    
                Assert.That(to.Name, Is.EqualTo(from.Name));
                Assert.That(to.Age, Is.EqualTo(from.Age));
            }
        }
    
        public class ObjectFrom
        {
            public string Name { get; set; }
            public int Age { get; set; }
        }
    
        public class ObjectTo
        {
            private readonly string _name;
    
            public ObjectTo(string name)
            {
                _name = name;
            }
    
            public string Name
            {
                get { return _name; }
            }
    
            public int Age { get; set; }
        }
    }
    

    【讨论】:

    • 我猜“ConstructUsing”必须在比我们正在使用的版本更新的自动映射器中。谢谢乔恩
    • 非常感谢乔恩的这个例子。 “ConstructUsing”很棒!允许我使用标记为私有的设置器来保持我的 DTO 不可变。
    • 对我来说是一种享受; AutoMapper 目前不喜欢所有参数都是可选的构造函数,所以我只使用 .ConstructUsing(x => new MyClass());
    • 试过类似的 Mapper.CreateMap().ConstructUsing(x => new OrderViewModel(this)); ...得到一个“调用不明确”的编译器错误
    • 如果我需要传递比string 更复杂的东西怎么办?如果ObjectFrom 包含必须传递给ObjectTo 构造函数的ChildObjectFrom 类型属性怎么办?
    【解决方案2】:

    最佳做法是使用 AutoMapper 中记录的方法 http://docs.automapper.org/en/stable/Construction.html

    public class SourceDto
    {
            public SourceDto(int valueParamSomeOtherName)
            {
                Value = valueParamSomeOtherName;
            }
    
            public int Value { get; }
    }
    
    Mapper.Initialize(cfg => cfg.CreateMap<Source, SourceDto>()
      .ForCtorParam(
        "valueParamSomeOtherName", 
        opt => opt.MapFrom(src => src.Value)
      )
    );
    

    【讨论】:

      【解决方案3】:

      您应该使用Map 方法来设置目标。例如:

      Mapper.CreateMap<ObjectFrom, ObjectTo>()
      
      var from = new ObjectFrom { Name = "Jon", Age = 25 };
      
      var to = Mapper.Map(from, new ObjectTo(param1));
      

      【讨论】:

        【解决方案4】:

        在编写此答案时,如果属性与构造函数参数匹配,AutoMapper 将自动为您执行此操作(通过简单的CreateMap&lt;&gt;() 调用)。当然,如果事情不匹配,那么使用.ConstructUsing(...) 是要走的路。

        public class PersonViewModel
        {
            public int Id { get; set; }
        
            public string Name { get; set; }
        }
        
        public class Person
        {
            public Person (int id, string name)
            {
                Id = id;
                Name = name;
            }
        
            public int Id { get; }
        
            public string Name { get; }
        }
        
        public class PersonProfile : Profile
        {
            public PersonProfile()
            {
                CreateMap<PersonViewModel, Person>();
            }
        }
        

        注意:这里假设您使用Profiles 来设置您的自动映射器映射。

        当像下面这样使用时,这会产生正确的对象:

        var model = new PersonViewModel
        {
            Id = 1
            Name = "John Smith"
        }
        
        // will correctly call the (id, name) constructor of Person
        _mapper.Map<Person>(model);
        

        您可以在官方wiki on GitHub 中阅读有关自动映射器构造的更多信息

        【讨论】:

        • 看起来 CreateMap 应该是 PersonViewModel 而不是 PersonProfile。以及在第二个代码块中 PersonModel 应该是 PersonViewModel。
        【解决方案5】:

        就我个人而言,在使用 AutoMapper 时,我总是希望尽可能明确,以避免将来出现任何潜在的错误。

        如果您调用ConstructUsing 方法只是按照正确的顺序一个一个地传递参数,您可能有一天会遇到错误。

        如果开发人员在一些现有的可选参数之前反转了 2 个字符串参数或添加了一个新的可选参数怎么办?你会得到一个映射错误,其中一个属性没有映射到它应该映射到的目标属性。 出于这个原因,我更喜欢在实例化我的对象时使用命名参数来定义我的映射。

        以下是ConstructUsing 方法的不同签名:

        TMappingExpression ConstructUsing(Func<TSource, ResolutionContext, TDestination> ctor);
        TMappingExpression ConstructUsing(Expression<Func<TSource, TDestination>> ctor);
        

        在这种情况下,我们希望使用第一个,因为无法在表达式树中使用命名参数(您会收到编译错误 an expression tree may not contain a named argument specification)。

        这里是如何使用它:

         CreateMap<FromType, ToType>()
            .ConstructUsing((src, res) =>
            {
                return new ToType(
                    foo: src.MyFoo,
                    bar: res.Mapper.Map<BarModel>(src.MyBar),
                );
            });
        

        注意 Func 的第二个参数res,即Resolution Context。此参数允许您使用已注册的映射。

        但请注意,我想提醒您注意使用构造函数声明映射的一个缺点。如果您的类没有公共设置器(只读属性或private set),您将无法使用Map 方法的以下重载:

        TDestination Map<TSource, TDestination>(TSource source, TDestination destination);
        

        例如,在使用 EF Core 更新实体时,此重载可能非常方便

        mapper.Map(updateModel, existingEntity);
        await dbContext.SaveChangesAsync();
        

        幸好有another way to update entities with EF Core

        【讨论】:

        • Reg "如果开发人员反转 2 个字符串参数怎么办" 我的理解是参数的名称很重要,而不是顺序。
        • 我今天学到的东西:在ConstructUsing 中有一个映射器。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-07-01
        • 2020-05-01
        • 2022-10-08
        • 2011-12-29
        • 1970-01-01
        • 2022-01-18
        • 1970-01-01
        相关资源
        最近更新 更多