【发布时间】:2011-01-15 09:33:57
【问题描述】:
如果我的目标设置器是私有的,我可能想使用目标对象的构造函数映射到该对象。你会如何使用 Automapper 来做到这一点?
【问题讨论】:
如果我的目标设置器是私有的,我可能想使用目标对象的构造函数映射到该对象。你会如何使用 Automapper 来做到这一点?
【问题讨论】:
使用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; }
}
}
【讨论】:
string 更复杂的东西怎么办?如果ObjectFrom 包含必须传递给ObjectTo 构造函数的ChildObjectFrom 类型属性怎么办?
最佳做法是使用 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)
)
);
【讨论】:
您应该使用Map 方法来设置目标。例如:
Mapper.CreateMap<ObjectFrom, ObjectTo>()
var from = new ObjectFrom { Name = "Jon", Age = 25 };
var to = Mapper.Map(from, new ObjectTo(param1));
【讨论】:
在编写此答案时,如果属性与构造函数参数匹配,AutoMapper 将自动为您执行此操作(通过简单的CreateMap<>() 调用)。当然,如果事情不匹配,那么使用.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 中阅读有关自动映射器构造的更多信息
【讨论】:
就我个人而言,在使用 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();
【讨论】:
ConstructUsing 中有一个映射器。