【问题标题】:Repository with Multiple Entities Creating a DTO具有多个实体的存储库创建 DTO
【发布时间】:2016-02-01 12:34:54
【问题描述】:

在此处进行一些 C# 代码审查,并注意到开发人员正在与 5 个额外的表进行连接,并将内联 IQueryable 投影到 DTO。在存储库中加入的表是 UserAppointmentCommunicationPreferenceUserProductProduct。我没有设计这个系统,我可能会稍微简化它,但让我们现在就这样吧。使用实体框架和 Web API。

这有点长,所以我很抱歉,但真的很想听听其他人对此事的看法。

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

因此,开发人员在存储库本身中将这些表连接在一起,然后创建一个名为“UserServiceOverviewDto”的 DTO,该 DTO 返回到控制器,然后返回到前端,基本上有信息以网格形式显示给客户代表(谁用户是,即将到来的约会,什么产品等等,基本上都是我上面写的)。

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

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

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

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

总之,问题是:

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

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

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

【问题讨论】:

    标签: c# asp.net-web-api architecture domain-driven-design repository-pattern


    【解决方案1】:

    除了这里的答案之外,我还会为您的应用程序研究一般的数据缓存。它可能通过在应用程序中缓存对象、减少数据库命中率提供一个很好的解决方案,同时还不如实现 CQRS。

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

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

    【讨论】:

      【解决方案2】:

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

      您描述的情况在实现统计功能时经常发生。例如。回答诸如“有多少用户订购了此产品,按天分组?”之类的问题。这有时被称为用例优化查询

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

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

      新类型的设计注意事项

      • 返回的数据始终是只读的,因此可以将类型建模为值对象
      • 如果类型在域中有意义,则放在域层。如果没有,就放到持久层。

      【讨论】:

      • 非常感谢 - 听到你说这话很有见地。说实话,我正在阅读 CQRS 并感到有点不知所措。也许创建新实体是要走的路?
      • 不,实体具有 ID 和可变状态,但手头的案例返回聚合的、不可变的数据。所以它是一个值对象,而不是一个实体。
      【解决方案3】:

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

      【讨论】:

      • 非常感谢 - 在我今天阅读的内容和你给我的这个链接之间,我想我有足够的能力来解决这个问题 :) 真的很感激。我不知道 CQRS 是我所追求的。
      • 这个问题与CQRS无关。 CQRS 也解决了这类问题,但你认为用不同的架构重建整个应用程序是否适合解决这样的小规模设计问题?我没有。
      猜你喜欢
      • 1970-01-01
      • 2015-10-07
      • 2012-08-21
      • 2019-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-18
      相关资源
      最近更新 更多