具有多个实体的存储库创建DTO
本文关键字:存储 创建 DTO 实体 | 更新日期: 2023-09-27 18:20:02
在这里做了一些C#代码审查,注意到一位开发人员正在与5个额外的表进行连接,并将内联IQueryable投影到DTO中。存储库中加入的表为User、Appointment和Product。我没有设计这个系统,我可能会在一定程度上简化它,但让我们现在就这样做吧。使用实体框架和Web API。
这有点长,所以我道歉,但真的很好奇听到别人对这件事的看法。
因此,这些表的关系在于,每个用户都有一个即将到来的服务预约列表,以服务他们的产品。UserProduct表显示产品和用户之间的关系,product表显示产品的种类。CommunicationPreference涉及他们希望如何联系,比如他们的服务何时到期,如果有人来为产品提供服务,他们希望如何与他们联系(电话电子邮件文本等)。
因此,开发人员在存储库中将这些表连接在一起,然后创建一个名为"UserServiceOverviewDto"的DTO,该DTO返回到控制器,然后返回到前端,基本上有信息在网格中显示给客户代表(用户是谁、即将到来的约会、什么产品等,基本上是我上面写的所有内容)。
我理解他们为什么这么做——他们没有对5个不同的存储库进行5次调用,而是在一个存储库中完成所有操作,然后他们不必将每个存储库转换为DTO,而是一次性完成所有操作。但我知道存储库也不应该返回DTO。理想情况下,您的存储库应该返回逻辑上聚合的、代表一个实体的东西,以解决您的问题。
当我思考这个问题时,我在SO中偶然发现了这个问题。当查询存储库层中的多个实体时,返回什么类型?但这对我的帮助还不够,我想征求更多关于该怎么做的建议。
从DDD的角度来看,我想知道需要引入什么新的"东西"。也许我需要创建一个新的上下文/存储库,这就是新事物。如果我想说这是一个常见的分组,并给它一个像"X"这样的名称,这是否意味着我想创建一个新的数据库表来表示它?可能不会,因为我已经有了存储这些信息的表。
这是否意味着我想创建一个名为"X"的新类,而不需要相应的数据库表?那么,这与DTO有什么不同呢?事实上,这几乎表明,通过创建这个类,我试图通过实例化X而不是XDto,然后将X强制转换为服务层中的DTO,使我的存储库更加纯粹(除了转换内容和创建一个非常缺乏活力的实体之外,我没有获得任何其他东西)。
总之,问题是:
如果我有多个实体,为了方便起见,我在前端将它们分组在一起,是不是更好:
- 是否让服务层进行5个不同的数据库调用,并在该层中组装一个DTO以返回到控制器?这不是很糟糕吗?5个调用只是为了让我的代码看起来更"纯粹"?一次加入并从数据库中获取所需信息不是更好吗
- 在C#中创建一个新类,表示这5个表的结合,然后实现这个类和相应DTO之间的映射。这门课不会像我上面说的那样坚持下去
- 让存储库执行联接以创建一个新的DTO作为内联投影(似乎错误的b/c存储库应该返回一个实体而不是DTO)
真的很期待这里的反馈!谢谢
这个问题不是CQRS特有的,在它之外也是有效的,所以我会这样回答。
您所描述的情况经常发生在实现统计功能时。例如,为了回答诸如"有多少用户订购了此产品,按天分组?"之类的问题,这有时被称为用例优化查询。
在这些情况下,你建议的第二种方法通常是最好的创建一个新类型,对此类操作的结果进行建模。聚合存储库中的所有数据并返回该类型的实例
解决方案#1通常效率非常低,因为服务层无法使用DB提供的聚合和分组功能。解决方案#3违反了单一责任原则,迟早会导致可维护性问题。
新型的设计注意事项
- 返回的数据始终是只读的,因此类型可以建模为值对象
- 如果类型在域中有意义,请将其放在域层中。如果没有,请将其放入持久层
看看CQRS,它几乎是对这个主题的精确讨论。基本上,您可能应该创建一个新对象来表示这个聚合,但要知道这样做会给您的项目带来很大的复杂性。您创建的这个东西无法更新/持久化,只能查询。必须通过单独的域模型来更新与之关联的这些其他实体。
除了这里的答案之外,我还将研究您的应用程序的一般数据缓存。它可以通过在应用程序中缓存对象、减少数据库命中率,而不至于实现CQRS,从而提供一个很好的解决方案。
我肯定会使用memcached、redis、AppFabric或内置的.NET MemoryCache等来在用户登录时自动将用户的通信首选项和用户对象缓存在内存中。
您还可以使用数据缓存将公司的产品缓存在内存中。当更新、删除产品等时,您必须使缓存项无效。