具有多个实体的存储库创建 DTO

Repository with Multiple Entities Creating a DTO

在此处进行一些 C# 代码审查,并注意到一名开发人员正在执行一个与 5 个额外 table 的连接,以及一个到 DTO 的内联 IQueryable 投影。加入存储库的 table 是 UserAppointmentCommunicationPreferenceUserProductProduct。我没有设计系统,我可能会稍微简化它,但让我们暂时按原样使用它。使用 Entity Framework 和网络 API.

这篇文章有点长,所以我深表歉意,但真的很想听听其他人对此事的看法。

因此,这些 table 的相关性在于每个用户都有一个即将到来的服务预约列表来为其产品提供服务。 UserProduct table 显示产品与用户之间的关系,product table 显示产品的种类。 CommunicationPreference 处理他们希望如何就服务到期时联系他们,如果有人来服务产品,他们希望如何联系(phone 电子邮件文本等)。

因此开发人员将这些 table 加入到存储库本身中,然后创建一个名为 "UserServiceOverviewDto" 的 DTO returned 到控制器,然后是前端和基本上有信息要在网格中显示给客户代表(用户是谁,即将到来的约会,什么产品,等等。基本上我刚刚在上面写的所有内容)。

我理解他们为什么这样做 - 他们不是对 5 个不同的存储库进行 5 次调用,而是一次完成所有操作,然后不必将每个存储库都转换为 DTO,他们一次性完成了所有操作。但我知道存储库也不应该 return DTO。理想情况下,您的存储库应该 return 逻辑上聚合并代表解决您问题的一个实体的东西。

当我在考虑这个问题时,我在 SO What type to return when querying multiple entities in Repository layer? 中偶然发现了这个,但它并没有真正帮助我,想就如何做寻求更多建议。

从 DDD 的角度来看,我想知道需要引入什么新的 "thing"。也许我需要创建一个新的 context/repository,这是一个新事物。如果我想说这是一个常见的分组并给它起一个像 "X" 这样的名称,这是否意味着我想创建一个新的数据库 table 来表示它?可能不是,因为我已经有了存储此信息的 table。

这是否意味着我想创建一个名为 "X" 的新 class 而没有相应的数据库 table?那么这与 DTO 有什么不同呢?事实上,它几乎表明通过制作这个 class,我试图通过实例化 X 而不是 XDto 来使我的存储库更纯粹,然后无论如何将 X 转换为服务层中的 DTO(我还没有获得除了四处移动东西和创建一个非常贫血的实体之外的任何事情)。

那么综上所述,问题是:

如果我有多个实体,为了前端的方便而组合在一起,是否更好:

  1. 让服务层进行 5 次不同的数据库调用,并且 assemble 该层中的 DTO return 到控制器?这不是很糟糕吗? 5 个调用只是为了让我的代码看起来更 "pure"?一次完成连接并从数据库中取回所需内容不是更好吗?
  2. 在C#中新建一个class代表这5个table的联姻,然后在这个class和对应的DTO之间实现一个映射。这个class不会像我上面说的那样被持久化
  3. 让存储库执行其连接以创建新的 DTO 作为内联投影(似乎是错误的 b/c 存储库应该 return 一个实体而不是 DTO)

真的很期待这里的一些反馈!谢谢!

看看CQRS,几乎是对这个话题的准确讨论。基本上,您可能应该创建一个新对象来表示此聚合,但要知道这样做会给您的项目带来很大的复杂性。你创建的这个东西不能updated/persisted,只能查询。更新与之关联的这些其他实体必须通过单独的域模型完成。

这个问题不是特定于 CQRS 的,在它之外也有效,所以我会这样回答。

您描述的情况在实现统计功能时经常出现。例如。回答诸如 "how many users ordered this product, grouped by day?" 之类的问题,这有时称为 用例最优查询

在这些情况下,您建议的第二种方法通常是最好的。 创建一个新类型来模拟此类操作的结果。聚合存储库中的所有数据和 return 该类型的实例。

解决方案 #1 通常非常 效率低下,因为服务层无法使用数据库提供的聚合和分组功能。解决方案 #3 违反了单一责任原则,迟早会导致可维护性问题。

新类型的设计注意事项

  • returned数据总是只读的,所以类型可以建模为值对象.
  • 如果类型在领域中有意义,就把它放在领域层。如果没有,就放在持久层。

除了此处的答案之外,我还会研究您的应用程序的一般数据缓存。它可能通过在应用程序中缓存对象、减少数据库命中率而不是实施 CQRS 来提供一个很好的解决方案。

我肯定会使用 memcached、redis、AppFabric 或内置的 .NET MemoryCache 等来在用户登录时自动将用户的通信首选项和用户对象等内容缓存在内存中。

您还可以使用数据缓存将您公司的产品缓存在内存中。当产品更新、删除等时,您必须使缓存项无效。