Automapper 和不可变性

11

能否将AutoMapper用于Immutable类型?

例如,我的领域类型是不可变的,我想将我的视图类型映射到这个类型。

我相信它不能,但只是想要确认一下。

此外,最佳实践是使领域类型不可变,那么在将视图类型映射到领域类型时,最佳实践是什么?


域类型不可变?我不认为这是正确的说法。 - David
5个回答

8

通常我会手动将视图类型映射到领域类型,因为我通常会通过更复杂的接口、使用方法等方式进行工作。如果您使用AutoMapper从视图转换到领域,则无论您是否有意决定,您现在都被锁定在贫血领域模型中。


如果你使用Automapper从视图映射到领域,为什么会说领域模型是贫血的呢?你需要以某种方式填充领域模型中来自视图的数据。这是因为你建议通过构造函数/方法创建/填充领域吗? - Per Hornshøj-Schierbeck
1
没错,看看我的系列文章,加强你的领域知识: http://www.lostechies.com/blogs/jimmy_bogard/archive/2010/02/03/strengthening-your-domain-a-primer.aspx如果你看其他MVC框架,比如Rails,模型的概念是一个持久化模型,所以你不需要AutoMapper。 - Jimmy Bogard
我认为你的文章让我改变了想法,我经常使用 AutoMapper 将领域对象映射到 DTO/VM,但总是不得不查找如何使 AutoMapper 做一些简单的属性映射之外的事情。手动编写代码不仅更易于阅读和调试,而且还可以避免这种情况。你是如何组织代码的 - 也就是说,你把映射对象属性的方法放在哪里? - sambomartin

2
假设您确实希望在域类型上拥有一个不可变的属性,比如Id。您的域类型可能如下所示:
public class DomainType
{
    public DomainType(int id)
    {
        Id = id;
    }

    public int Id { get; }
    // other mutable properties
    // ...
}

然后您可以使用ConstructUsing使用您选择的公共构造函数,例如:

CreateMap<ViewType, DomainType>()
    .ConstructUsing(vt => new DomainType(vt.Id));

然后按照常规方式映射所有可变属性。

如果ViewType也包含一个名为“Id”的属性,则无需使用.ConstructUsing()。AutoMapper将自动使用构造函数并将参数名称与源属性名称匹配。 - 9Rune5
这是一个我可以接受的答案。谢谢。 - Michael Puckett II

1

AutoMapper依赖于属性设置器来完成其工作,因此如果您有只读属性,则AutoMapper将没有太大用处。

您可以覆盖映射行为,并配置它以调用特定的构造函数,但这基本上会破坏AutoMapper的目的,因为那样您就需要手动进行映射,并且在过程中添加了一个笨拙的额外步骤。

对我来说,您的领域模型是不可变的并不太合理。您如何更新它?整个应用程序都是只读的吗?如果是这样,为什么您需要映射到您的领域模型而不是从您的领域模型呢?一个不可变的领域模型听起来...相当无用。

P.S. 我假设您指的是这个AutoMapper,而不是Fluent NHibernate中的自动映射功能或其他完全不同的东西。如果我理解有误,请更具体地说明并为您的平台/语言添加标签。


2
“对我来说,你的领域模型是不可变的这个概念并不太合理。”——这是有道理的。比如说,假设你正在使用CQRS,那么读模型应该是不可变的。另一个例子是值对象。 - Fabio Marreco
我有些不同意这个评论并道歉,但是不可变的DTO是有效的,使用标准映射器也同样有效。您希望您的映射逻辑是通用的,因此无论是映射byte[]、不可变类型、字符串还是其他任何东西,我建议将其标准化。使用AutoMapper可能对于所有类型的映射来说有点过度,但是使用相同的映射接口不是。我认为AutoMapper应该寻找并尝试使用构造函数映射,如果字段是私有的,但是您甚至可以使用反射设置只读字段,所以我的直觉告诉我这是可能的。 - Michael Puckett II

1

4
你已经在三篇不同的帖子中发布了此链接,几乎没有任何额外信息。虽然答案可能会引用外部资源,但它们也应该是自包含的,以便读者不依赖于它们。此外,如果这恰好是你的博客,你应该确保查看 Stack Overflow 的自我推广规则。 - Jeremy Caney
由于当前的回答写得不够清晰,请 [编辑] 添加更多细节,以便其他人了解如何解决问题。您可以在帮助中心找到有关编写良好答案的更多信息。 - Community

1
我们使用构建者模式创建不可变对象。将它们映射需要写更多的样板代码,但是这是可以实现的。
// ViewModel
public class CarModel : IVehicleModel 
{
    private CarModel (Builder builder)
    {
        LicensePlate = builder.LicensePlate;
    }    

    public string LicensePlate { get; }

    //
    public Builder
    {
        public string LicensePlate { get; set; }
    }
}


// Model
public class CarViewModel : IVehicleViewModel
{
    private CarViewModel (Builder builder)
    {
        LicensePlate = builder.LicensePlate ;
    }    

    public ILicensePlate LicensePlate { get; }

    //
    public Builder
    {
        public ILicensePlate LicensePlate { get; set; }
    }
}

我们的 AutoMapper 配置文件中注册了三个映射:
        CreateMap<IVehicleModel, CarViewModel.Builder>();
        CreateMap<CarViewModel.Builder, IVehicleViewModel>().ConvertUsing(x => x.Build());
        CreateMap<IVehicleModel, IVehicleViewModel>().ConvertUsing<VehicleModelTypeConverter>();

VehicleModelTypeConverter 然后定义了一个两阶段转换:

    public IVehicleViewModel Convert(IVehicleModel source, IVehicleViewModel destination,
        ResolutionContext context)
    {
        var builder = context.Mapper.Map<CarViewModel.Builder>(source);
        var model = context.Mapper.Map<IVehicleViewModel>(builder);

        return model;
    }

实现了对于 ITypeListConverter<string, ILicensePlate> 的转换。

在我们的系统中使用与普通方法相同:

var result = _mapper<IVehicleViewModel>(_carModel);

这里使用的是AutoMapper v7.0.1


@LucianBargaoanu 那篇文章涵盖了使用公共构造函数参数构建对象的内容,但我认为这对我不起作用。 - Morphed
它不必是公共的。 - Lucian Bargaoanu

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