将实体对象用作数据传输对象是一种好习惯吗?


29

我想知道,如果是这样,为什么实体框架不提供逻辑来创建具有相同属性的新对象以在层之间传输数据?

我使用在实体框架中生成的实体对象。


3
我认为这是一个很好的问题,但我不能真正说出来,因为它很难阅读,所以不确定如何解决。请编辑您的问题。
candied_orange

1
@CandiedOrange +1,这使得如此之多的投票更加令人恐惧。
guillaume31 '18

Answers:


23

它是由你决定。

大多数人会告诉您这不是一个好习惯,但在某些情况下您可以避免。

EF从来没有与DDD很好地结合使用,原因有很多,但有两个很突出:您不能在实体上使用参数化的构造函数,也不能封装集合。DDD依赖于此,因为域模型应同时包含数据和行为。

在某种程度上,EF会强制您使用贫血领域模型,在这种情况下,您可以将实体用作DTO。如果使用导航属性,则可能会遇到一些问题,但是您可以序列化这些实体并通过网络发送它们。不过,这可能不切实际。您将必须控制具有不需要传递属性的每个实体的序列化。更简单的方法是简单地设计为数据传输量身定制的单独的类。为此创建了类似AutoMapper的库。

例如:假设您有一个Person具有以下定义的类:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; get; }

    // plus a bunch of other properties relevant about a person
}

假设你想不想找个显示员工列表,它可能是实际只是发送IdFirstNameLastName。但是,您必须发送所有其他不相关的属性。如果您不关心响应的大小,那么这并不是什么大问题,但是通常的想法是仅发送相关数据。另一方面,您可以设计一个API,该API返回人员列表,在这种情况下,可能需要发送所有属性,从而可以序列化和发送实体。在这种情况下,创建DTO类是有争议的。有些人喜欢混合实体和DTO,有些则不喜欢。

为了回答您的最新问题,EF是一个ORM。它的工作是将数据库记录映射到对象,反之亦然。在通过EF之前和之后对这些对象执行的操作不是其关注的一部分。也不应该。


1
简要总结一下。DTO和POCO有什么区别?他们不只是保存数据吗?
Laiv

1
@ guillaume31如果不考虑持久性,就无法拥有真正的领域模型。如果我被迫使用反射(更不用说其他类型的骇客了,只要我的公共属性由另一个不同名称的私有字段支持)来将聚合加载到有效状态,那么公共接口有什么用?如果EF没有为我提供通过使用其公共接口来隐藏聚合的基础结构,那么我最好不要将业务逻辑保留在其他位置,并让我的域处于贫乏状态,而不是编写额外的样板从数据库中加载它们。
devnull

1
因此,您宁愿使整个域模型失去作用,而不是像使用私有字段一样命名您的公共收藏夹?(让我们将构造函数问题放在一边,因为最常见的情况是首先暴露于域中,以暴露构造函数并接收所有对象的字段...)
guillaume31 '18

1
@Konrad从这里开始As of EF Core 2.1, only services known by EF Core can be injected. Support for injecting application services is being considered for a future release.我非常喜欢将应用程序服务注入到我的实体中。
devnull

1
@Konrad我将停止使用域实体作为DTO(如果我以这种方式使用它们)。无论EF对服务注入的支持如何,DTO都是有用的。
devnull

8

不它不是。

理想情况下,DTO将与您的持久性存储库(也就是您的数据库表)匹配。

但是您的业务类别不一定匹配。您可能需要其他类,或者将分离的类或加入的类添加到数据库中。如果您的应用程序很小,则可能不会真正看到这种问题,但是在大中型应用程序中,这种情况经常发生。

另一件事是,DTO是处理持久性的领域的一部分,而您的业务层对此一无所知。


一个命令对象是DTO,业务层应该对此有所了解。
devnull

我认为业务层可能会通过接口间接调用命令对象。我的意思是DTO就像携带数据的简单对象一样,根本没有任何功能。我不确定命令对象是否是DTO(martinfowler.com/eaaCatalog/dataTransferObject.html)。
Juan Carlos Eduardo Romaina Ac

1
通常,命令分为实际命令及其处理程序。该命令包含数据,处理程序包含行为。像这样使用时,命令部分仅包含从客户端接收的数据,并充当客户端和业务层之间的DTO。
devnull

DTO不会从持久性流向领域,它们也可能来自外部,就像在发送数据时一样(在REST API中考虑)。正如devnull指出的那样,命令和事件在某种程度上是DTO,并且它们已转移到业务层。处理程序是否在业务层取决于设计。
2015年

4

这实际上是一个非常糟糕的主意。马丁·福勒(Martin Fowler)撰写了有关本地DTO文章

长话短说,DTOPattern用于在流程外(例如,通过电线而不是在同一流程内的各层之间)传输数据。


与所有编程方法一样,没有一个适合所有人的规模,否则我们不会在构建的每个应用程序中辩论和讨论不同的模式和方法,我们只会使用相同的东西。DTO本质上只是POPO,它们的名称只是为了显示一些意图。使用DTO在服务,控制器和视图之间“传输数据”并没有什么用。类的名称或结构中没有任何内容指示或限制从何处传输数据以及从何处传输数据
詹姆斯·

4

不,这是一个坏习惯。

原因如下:

  1. 默认情况下,新实体字段将在Dto中。使用实体意味着默认情况下将可使用所有信息。这可能会导致您暴露明智的信息,或者至少使您的API合同膨胀,其中包含许多不为谁使用API​​的信息。当然,您可以使用一些注释来忽略字段(例如@JsonIgnore来自Java世界),但这会导致下一个问题...
  2. 关于实体的许多注释/条件/控制。您需要控制在Dto上要发送的内容,当某些属性名称更改时(这会破坏合同),添加一些不是来自实体的属性,属性的顺序等。不久,您将看到简单的具有大量注释,额外字段和每次的实体将很难理解正在发生的事情。
  3. 灵活性较差。使用Dto,您可以自由地在更多类中拆分信息,更改属性名称,添加新属性等。对于像Dto这样的实体,您很难做到这一点。
  4. 没有优化。您的实体将永远比简单的Dto大。因此,您总是会有更多的信息被忽略/序列化,并且可能还会传输更多不必要的信息。
  5. 懒惰的问题。使用某些框架的实体(例如Hibernate),如果您尝试检索以前未在数据库事务中延迟加载的某些信息,则ORM代理将不会附加到该事务,并且您将收到某种“延迟异常”只是调用get实体的方法。

因此,使用某种映射器工具可以更轻松,更安全地帮助您完成此任务,将实体字段映射到Dto。


1

为了完成@Dherik所说的,将实体对象用作数据传输对象的主要问题是:

  1. 在事务中,您冒着提交对实体所做的更改的风险,因为您将其用作DTO(即使您可以在事务中分离会话的实体,大多数情况下,您需要先检查此状态才能实体-DTO上的任何修改,并确保您不参与交易或如果您不希望这些修改被保留,则确保会话已关闭)。

  2. 您在客户端和服务器之间共享的数据大小:有时,您不想将实体的所有内容发送到客户端,以最大程度地减少请求响应的大小。将DTO与实体分开更加灵活,从而可以在某些用例中专门化要发送的数据。

  3. 可见性和维护性:您必须在实体的字段上管理jpa / hibernate批注,并维护jackson批注以在json的同一位置进行序列化(即使您可以将它们与实体实现分开,也可以将它们放入由继承的接口中)实体)。然后,如果您在添加新字段时更改了DTO内容,那么另一个人可能会认为这是实体的字段,因此可能是数据库中相关表的字段(即使您可以@Transient在所有DTO字段上使用注释)对于这种情况..!)。

我认为,当您阅读实体时,它会产生噪音,但是我的观点肯定是主观的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.