【问题标题】:Constructing a Domain Object from multiple DTOs从多个 DTO 构造域对象
【发布时间】:2023-04-02 05:06:01
【问题描述】:

假设您有规范的客户域对象。您可以在三个不同的屏幕上显示客户:外部管理员、内部管理员和更新帐户。

进一步假设每个屏幕仅显示 Customer 对象中包含的所有数据的一个子集。

问题是:当 UI 从每个屏幕传回数据时(例如,通过 DTO),它只包含完整客户域对象的子集。因此,当您将该 DTO 发送到客户工厂以重新创建客户对象时,您只有客户的一部分。

然后您将此客户发送到您的客户存储库以保存它,一堆数据将被清除,因为它不存在。悲剧接踵而至。

所以问题是:你将如何处理这个问题?

我的一些想法:

  • 在 存储库指示哪个部分 客户更新,并忽略 其他

  • 当您加载客户时,将其保存在静态内存中、会话中或任何地方,然后当您从 UI 接收到其中一个 DTO 时,仅更新与 DTO 相关的部分

IMO,这两个都是杂牌。还有其他更好的想法吗?

@chadmyers:这就是问题所在。

实体具有属性 A、B、C 和 D。

DTO #1 包含 B 和 C 的属性。

DTO #2 包含 C 和 D 的属性。

UI 要求 DTO #1,您从存储库加载实体,将其转换为 DTO #1,仅填写 B 和 C,然后将其提供给 UI。

现在 UI 更新 B 并将 DTO 发回。您重新创建实体,它只填写了 B 和 C,因为这就是 DTO 中包含的全部内容。

现在您要保存仅填充了 B 和 C 的实体,其中 A 和 D 为空/空白。存储库无法知道它是否应该将持久性中的 A 和 D 更新为空白,或者是否应该忽略它们。

【问题讨论】:

    标签: repository-pattern dto domain-object


    【解决方案1】:

    在收到 DTO 后,我会使用工厂从存储库中加载完整的客户对象。之后,您只能更新 DTO 中指定的那些字段。

    这还允许您通过检查最后更新的时间戳对您的客户应用一些乐观并发。

    【讨论】:

    • 有趣的方法。但是,您正在交易一些性能,因为您有两次到存储库的往返而不是一次。
    • 谨防在存储库级别之上进行优化,因为您允许数据​​库优化思想通过存储库抽象泄漏。如果性能成为问题,您可以稍后应用二级缓存或其他此类技术
    • 在你的代码运行之前要小心优化你的代码。此外,如果您需要每纳秒的吞吐量,为什么还要使用对象?
    • 你是对的,正义。众所周知,我是一个预优化器。我正在尝试戒烟。
    【解决方案2】:

    这是一个网络应用程序吗?从存储库加载客户对象,从 DTO 更新它,然后将其保存回来。这对我来说似乎不是一个杂物。 :)

    更新:根据您的更新(A、B、C、D 示例)

    所以我在想的是,当你加载实体时,它有 A、B、C 和 D 填充。如果 DTO#1 只更新 B 和 C,那没关系。 A 和 D 不受影响(这是理想的情况)。

    存储库如何处理 B & C 更新取决于他。例如,如果您使用的是 Hibernate/NHibernate,它会发现并发布更新。

    仅仅因为 DTO #1 只有 B 和 C 并不意味着您也必须取消 A 和 D。不要管它们。

    【讨论】:

    • 不管是不是网络应用。问题是没有“那个” DTO,而是不同的 DTO,它们都没有完全描述客户,只是其中的一部分。
    • 这很重要,因为如果它是有状态的,您可能会使用与部分 DTO 不同的方法,这就是我要探索的方法。而且 DTO 是否完整地描述它并不重要,它不需要。
    • 你是对的,一种选择是重新设计 DTO。我没有提到 DTO 的设计可能不在我手中的限制。您是否建议 Customer 对象实现 DTO 接口而不是单独的 DTO 类?
    • 不,我没有说重新设计 DTO(不过,对于非 Web 应用程序,您可能会使用完全差异化的方法)。我是说:您的 DTO 不需要与您的实体 1:1 映射,事实上,他们不应该。但是你也不应该尝试从 DTO 中创建一个完整的实体
    • 我不确定问题出在哪里。只需从存储库加载实体,应用 DTO 中需要应用的任何更改,再次保存实体。这对您不起作用,还是加载实体太昂贵或其他什么?
    【解决方案3】:

    起初我错过了这个问题的重点,因为它基于一些我认为从设计角度来看没有意义的事情。

    1. 从存储库中对实体进行水化然后将其转换为 DTO 是浪费精力。我假设您的 DAL 将 DTO 传递到您的存储库,然后将其转换为完整的实体对象。所以将其转换回 DTO 似乎很浪费。

    2. 如果您的搜索结果页面显示大量记录并且仅显示部分实体数据,则拥有多个 DTO 是有意义的。在这种情况下,只传递该页面所需的数据是有效的。将包含部分数据的 DTO 传递给 CRUD 页面是没有意义的。只需给它一个完整的 DTO 甚至一个完整的实体对象。如果它不使用所有数据,很好,不会造成任何伤害。

    所以主要问题是我认为您不应该使用部分 DTO 将数据传递到这些页面。如果您使用完整的 DTO,我会在执行保存操作时执行以下 3 个步骤:

    1. 从存储库或数据库中提取完整的 DTO
    2. 使用通过表单所做的任何更改更新 DTO
    3. 将完整的 DTO 保存回存储库或数据库

    此方法需要额外的数据库命中,但这在 CRUD 表单上确实不是一个重要问题。

    【讨论】:

      【解决方案4】:

      如果我们知道存储库处理(几乎完全)非常丰富的域实体,那么您众多的 DTO 可以简单地映射回来。

      dtoUser.MapFrom<In,Out>(Entity)
      or
      dtoAdmin.MapFrom<In,Out>(Entity)
      

      您将执行相反的操作以将 dto 信息返回给实体,依此类推。所以你的存储库只保存了丰富的实体而不是大量的 DTO

      entity.Foo = dtoUser.Foo
      or
      entity.Bar = dtoAdmin.Bar
      
      entityRepsotiry.Save(entity) <-- do not pass DTO.
      

      DTO 的全部意义在于保持演示的简单性,或者说 WCF 数据传输,它与存储库或实体无关。

      此外,您永远不应该从 DTO 构造实体......获取实体的唯一两种方法是分别通过工厂(新)或存储库(现有)。

      您提到将实体存储在某个地方,您为什么要这样做?这是您的存储库的工作。它将决定从哪里获取 Entity(db,cache,e.t.c),无需将其存储在其他地方。

      希望这有助于在您的域中分配责任,这始终是一个挑战,并且到处都有灰色区域,但总的来说,这些是存储库、DTO 等的典型用途

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-03-09
        • 1970-01-01
        • 1970-01-01
        • 2014-02-13
        • 2023-03-03
        • 2022-01-23
        • 2011-02-19
        相关资源
        最近更新 更多