AutoMapper 如何映射具有私有 setter 的属性

32

使用AutoMapper是否可以将属性指定为私有的setter?


你在使用哪个版本的Automapper?你尝试过做这件事吗? - PatrickSteele
11
@blockhead 实际上这是一个非常重要的问题,不可变对象比可以随意改变的对象(通过公开暴露它们的设置器)更好的架构实践。这正是Entity Framework最近添加了能够填充私有属性的功能的原因,以允许领域模型在初始填充后保持不变。也许需要改进架构的并不是 leozilla。;) - Marchy
对不起?私有属性应该是私有的。这是属于对象的数据,只有对象知道并且应该知道这些数据。 - blockhead
12
楼上的@blockhead并没有在谈论私有属性,他所说的是具有私有setter(set accessor)的公共/受保护属性。这是一个很大的区别。 - Beyers
3个回答

36

AutoMapper现在允许(我不确定是从何时开始)映射具有私有setter的属性。它使用反射来创建对象。

示例类:

public class Person
{
    public string Name { get; set; }
    public string Surname { get; set; }
}


public class PersonDto
{
    public string Fullname { get; private set; }
}

并且映射:

AutoMapper.Mapper.CreateMap<Person, PersonDto>()
    .ForMember(dest => dest.Fullname, conf => conf.MapFrom(src => src.Name + " " + src.Surname));

var p = new Person()
{
    Name = "John",
    Surname = "Doe"
};

var pDto = AutoMapper.Mapper.Map<PersonDto>(p);
AutoMapper可以轻松地映射具有私有setter的属性。如果你想强制封装,你需要使用IgnoreAllPropertiesWithAnInaccessibleSetter。使用此选项后,所有私有属性(以及其他不可访问的属性)都将被忽略。
AutoMapper.Mapper.CreateMap<Person, PersonDto>()
    .ForMember(dest => dest.Fullname, conf => conf.MapFrom(src => src.Name + " " + src.Surname))
    .IgnoreAllPropertiesWithAnInaccessibleSetter();

如果您使用Silverlight,将会出现这个问题。根据MSDN:https://msdn.microsoft.com/en-us/library/stfy7tfc(v=VS.95).aspx

在Silverlight中,您无法使用反射来访问私有类型和成员。


3
有点棘手。ReSharper说private set可以被移除。但是当我这样做时,AutoMapper无法再映射该属性。它不会报错,只是将私有属性设置为其初始值! - comecme
2
@comecme,这是因为生成代码时使用私有设置和不使用私有设置是不同的。因此,没有生成任何可供Automapper通过反射来进行值设置的方法。Resharper无法分析代码通过反射访问属性的情况。这就是为什么Resharper建议可以删除私有设置方法的原因。 - Lejdholt
4
我明白为什么会发生这种情况,我只是想指出AutoMapper能够使用私有属性可能会带来一些麻烦,因为像ReSharper这样的工具(我猜其他静态代码分析器也是如此)可能认为可以安全地删除setter。请注意,这并不改变原意。 - comecme
1
@comecme 可能是在这些评论发布后的某个时候,JetBrains 创建了一个注释库,可以自动引入,并且您可以使用 [UsedImplicitly] 属性装饰此类属性。这告诉 ReSharper 某些内容是通过反射或其他框架行为间接使用的。 - Logarr

31

如果您像这样在构造函数中设置此属性的值

public class RestrictedName
{
    public RestrictedName(string name)
    {
        Name = name;
    }

    public string Name { get; private set; }
}

public class OpenName
{
    public string Name { get; set; }
}

那么你可以像这样使用ConstructUsing

Mapper.CreateMap<OpenName, RestrictedName>()
            .ConstructUsing(s => new RestrictedName(s.Name));

使用这段代码可以正常工作

var openName = new OpenName {Name = "a"};
var restrictedName = Mapper.Map<OpenName, RestrictedName>(openName);
Assert.AreEqual(openName.Name, restrictedName.Name);

4
最新版本似乎不需要这个。我可以使用私有setter映射到类中。它内部一定是使用反射。 - Monstieur
1
@Locutus,也许你可以把那个评论变成一个答案?它可能应该被标记为这个问题的新答案。 - julealgon

7
请参见#600私有/内部目标属性
解决方案:
public class PrivateInternalProfile {
    protected override Configure() {
        ShouldMapField = fieldInfo => true;
        ShouldMapProperty = propertyInfo => true;
        CreateMap<User, UserDto>(); //etc
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接