【问题标题】:Using AutoMapper to load entities from the database?使用 AutoMapper 从数据库中加载实体?
【发布时间】:2016-08-28 21:22:22
【问题描述】:

我读过的大部分内容(例如from the author)表明应该使用 AutoMapper 将实体映射到 DTO。它不应该从数据库中加载任何东西。

但是如果我有这个怎么办:

public class Customer {
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual ICollection<Order> Orders { get; set; }
}

public class CustomerDto {
  public int Id { get; set; }
  public string Name { get; set; }
  public IEnumerable<int> OrderIds { get; set; }   // here is the problem
}

我需要将 从 DTO 映射到实体(即从 CustomerDtoCustomer),但首先我必须使用该外键列表从数据库中加载相应的实体。 AutoMapper 可以通过custom converter 做到这一点。

我同意这感觉不对...但是有什么替代方案?将该逻辑粘贴到控制器、服务、存储库、某个管理器类中?所有这一切似乎都在将逻辑推向其他地方,在同一层。如果我这样做,我还必须手动执行映射!

从 DDD 的角度来看,DTO 不应该是域的一部分。所以 AutoMapper 也不是域的一部分,因为它知道那个 DTO。所以 AutoMapper 与控制器、服务等处于同一层。

那么将 DTO 到实体的逻辑(包括访问数据库,并可能引发异常)放入 AutoMapper 映射是否有意义?

编辑
@ChrisSimon 下面的精彩回答从 DDD 的角度解释了为什么我不应该这样做。从非 DDD 的角度来看,是否有令人信服的理由不使用 AutoMapper 从数据库加载?

【问题讨论】:

  • PS 这不是一个主观问题。我想知道人们在这种情况下会做什么——我想知道我的选择。甚至作者也认为他的工具除了基本的对象到对象映射之外不应该用来做任何事情。
  • 您的意思是 DTO - 实体映射还是其他方式?
  • @tomliversidge DTO-to-entity(但实体必须根据 DTO 中的 FK 从数据库加载其导航属性)
  • 这里的业务流程是什么?为什么需要加载完整的订单对象?
  • @tomliversidge 客户端应用 POST 一个新的 CustomerDto 到服务器,作为一个新的 Customer 实体插入到数据库中。但是该 DTO 包含导航属性的 FK (Order),因此必须先加载它们(并验证它们是否存在等),然后我才能插入父实体。

标签: c# entity-framework asp.net-web-api automapper


【解决方案1】:

首先,我将总结一下我对 DDD 中的实体的理解:

  1. 可以创建实体 - 通常使用工厂。这是它们生命周期的开始。
  2. 可以通过调用实体上的方法来改变实体 - 修改它们的状态。这就是它们在其生命周期中的进展方式。通过确保实体拥有自己的状态,并且只能通过调用其方法来修改其状态,控制实体状态的逻辑都在实体类中,业务逻辑分离更清晰,系统更易于维护。李>

使用 Automapper 将 Dto 转换为实体意味着实体放弃了对其状态的所有权。如果 dto 处于无效状态并且您将其直接映射到实体上,则实体最终可能会处于无效状态 - 您失去了使实体包含数据 + 逻辑的价值,这是 DDD 实体的基础。

要就您应该如何处理这个问题提出建议,我会问 - 您要实现的操作是什么? DDD 鼓励我们不要考虑 CRUD 操作,而是要考虑真实的业务流程,并在我们的实体上对其进行建模。在这种情况下,您似乎正在将 Orders 链接到 Customer 实体。

在应用程序服务中,我会有一个类似的方法:

void LinkOrdersToCustomer(CustomerDto dto)
{
    using (var dbTxn = _txnFactory.NewTransaction())
    {
        var customer = _customerRepository.Get(dto.Id);
        foreach (var orderId in dto.OrderIds)
        {
            var order = _orderRepository.Get(orderId);
            customer.LinkToOrder(order);
        }
        dbTxn.Save();
    }
}

在 LinkToOrder 方法中,我将有明确的逻辑来执行以下操作:

  • 检查订单不为空
  • 检查客户的状态是否允许添加订单(他们当前是否处于活动状态?他们的帐户是否已关闭?等等)
  • 检查订单是否确实属于客户(如果 orderId 引用的订单属于另一个客户会怎样?)
  • 询问订单(通过订单实体上的方法)是否处于添加到客户的有效状态。

只有这样我才会将它添加到客户订单的集合中。

这样,应用程序“流程”和基础架构管理包含在应用程序/服务层中,但真正的业务逻辑包含在域层中 - 在您的实体中。

如果上述要求与您的申请无关,您可能还有其他要求。如果没有,那么也许没有必要走 DDD 的路线——虽然 DDD 有很多要添加的东西,但它的开销通常只在具有大量复杂业务逻辑的系统中才值得。

这与您提出的问题无关,但我还建议您查看 Customer 和 Order 的建模。他们都是独立的Aggregates吗?如果是这样,将 Customer 建模为包含 Order 的集合可能会导致未来出现问题 - 当客户拥有一百万个订单时会发生什么?即使集合是延迟加载的,您也知道在某些时候会尝试加载它,然后您的性能就会出现。这里有一些关于聚合设计的精彩读物:http://dddcommunity.org/library/vernon_2011/,它建议按 Id 建模参考而不是参考。在您的情况下,您可能有一个 OrderId 的集合,甚至可能有一个全新的实体来表示链接 - CustomerOrderLink,它将有两个属性 - CustomerId 和 OrderId。那么您的任何实体都不会嵌入集合。

【讨论】:

  • 感谢您的深思熟虑和冗长的回复。从纯 DDD 的角度来看,您当然是正确的。我也想等待更多答案(严格来说不是 DDD)......
  • 在解释的第一部分中,您说实体可以通过工厂(或 DbContext)从数据库中实现。那么为什么不使用 AutoMapper 呢?我很欣赏你在说什么,但实际上,使用 AutoMapper 有什么不同?我假设从 DDD 的角度来看,它与工厂、服务等处于同一层。
  • 严格来说,工厂不会在实体的生命周期开始时实现实体——它是创建实体。 DbContext 可以从数据库中实现它(这样做是在传统 DDD 意义上扮演存储库的角色)。不同之处在于存储库的合约是在不修改实体状态的情况下存储和检索实体状态。由于持久性在您的应用程序的控制范围内,您知道虽然实体状态是持久的,但它并没有被修改。但是,从外部世界进入您的应用程序的 Dto 处于不受控制的状态。
  • 是的 - 如果你没有从显式方法中获得任何价值,你可能不需要 DDD - 它并不适用于所有应用程序。您所看到的只是“自定义映射”,DDD 系统将其视为业务流程,并通过实体中的业务逻辑传递请求,以确保遵循业务规则。这不仅仅是关于“纯度”——如果你有很多规则,你必须将它们放在一起(在实体中),否则就会失控,分散在不同的服务中。如果您没有这样的业务规则,我完全同意 - 实用主义获胜,我的建议是矫枉过正。
  • 哈!不用担心 - 我总是全力以赴。在需要 DDD 的地方,它是救命稻草,但在不需要的地方,它是在浪费时间和精力。我只在 DDD 的上下文中解决了您的问题,因为您标记了它,并在您的问题中引用了它。但我仍然鼓励您考虑是否在您的客户中包含一系列订单是您想要这样做的方式......在我看来,这就像一个潜伏的性能问题等着你在路上咬你。但这只是一个猜测 - 如果不了解您的系统和上下文,我可能完全错了。
猜你喜欢
  • 2014-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多