【问题标题】:C# DDD without EF没有 EF 的 C# DDD
【发布时间】:2021-12-30 06:07:48
【问题描述】:

我从 DDD 开始,我有一个疑问。假设这些类(我使用 C# 和 Net Framework 4.7):

public class Address : ValueObject
{
    // Properties.
    public string Street { get; }
    public int Number { get; }


    public Address(string street, int number)
    {
        Street = street;
        Number = number;
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Street;
        yield return Number;
    }
}

public class Customer : IAggregateRoot
{
    // Properties.
    public int Id { get; protected set; }
    public string Name { get; protected set; }
    public Address Address { get; protected set; }


    public Customer(string name, Address address)
    {
        if (string.IsNullOrEmpty(name)) throw new Exception(nameof(name));

        Name = name;
        Address = address ?? throw new Exception(nameof(address));
    }

    // For database purpose.
    protected Customer()
    {

    }
}

我有接口 ICustomerRepository 并且作为基础结构,我需要使用原始 SQL (SQL Server),我的意思是,没有 EF、NHibernate 等。我在存储库实现中使用的方法是定义一个类“CustomerBuilder " 从 Customer 继承,以便能够访问受保护的字段。有没有其他方法可以实现这一目标?因为对于我需要加载的每个实体,我都必须创建一个构建器并重复构造函数和变量映射。

谢谢!

【问题讨论】:

  • 为什么不能使用 ORM?这是一个非常严格的要求。
  • @DavidG 我们正在重构我们的应用程序,我们已经使用原始 SQL 查询实现了所有功能。我知道使用 ORM 要容易得多,但我想知道在我们需要继续使用原始 SQL 的情况下会发生什么。
  • 什么都不会“发生”,只是要困难得多。我建议使用 Dapper,因为这是 EF 和原始 SQL 之间的一个很好的中间点。它可以让你使用现有的查询并将它们映射到 C# 对象中。
  • @DavidG 我知道这更难,但我真的很想知道如何实现这种情况。我找不到任何没有 ORM 的例子????
  • 您可以使用通用包装器来访问非公共属性和方法,但您会失去智能感知,因为您必须使用字段、属性和方法名称。

标签: c# sql-server domain-driven-design


【解决方案1】:

您的问题没有一个单一的答案。我可以想到以下解决方案:

  1. 为每个聚合创建一个“状态 DTO”,这是一个具有公开您希望从聚合中持久保存的信息的属性的类。然后在接受此状态 DTO 的聚合类中创建工厂方法或构造函数。您的存储库将新建状态 DTO 并将其传递给聚合构造函数/工厂。

  2. 使用AutoMapper,与第一点类似,您需要一个 DTO 来最初从数据库加载信息。然后,不要将其传递给聚合以手动映射到聚合的属性,而是使用 AutoMapper 自动为您执行映射。

  3. 使用Reflection 构建您自己的映射器。您可以创建一系列辅助方法,以便通过名称和类型轻松地将属性映射到私有 setter。

从上面的 3 个解决方案中,我认为数字 1 是最干净的,但与您已经在做的相比,它并不能节省您太多的打字时间。选项 3 将更难设置,但一旦完成,您应该能够创建新的存储库而无需太多开销。但实际上,您正在编写一些可以通过安装 nugget 包免费获得的东西。

【讨论】:

  • 将 Automapper 与您的域逻辑耦合是非常危险的。 Automapper 旨在映射 DTO 而不是具有复杂构造逻辑和值对象的对象。通过这样做,您将不得不在聚合的封装上做出妥协,以使 Automapper 满意。
  • @MaximeGélinas 我建议将 AutoMapper 与域逻辑耦合。域逻辑甚至不会引用 AutoMapper,也不会以任何方式知道它。 Automapper 实际上是为这些场景构建的,并且有几个扩展库,github.com/AutoMapper/AutoMapper.Data 可能对实现我的提议很有用。此外,存储库不必处理“复杂的构造逻辑”或任何逻辑。
  • Jimmy Boggard 明确表示 Automapper 仅适用于 DTO,仅此而已。
  • @MaximeGélinas 我会说这是对他的错误引用。他反对将用户/API 输入映射到域对象。但无论如何,引用不是论据。所有 ORM 都会在 DB 列与(域)对象属性之间进行映射。 Automapper 使用与 ORM 几乎相同的技术,因此它可以用于相同的目的。正如你所说,它不会添加任何耦合。它只需要将域对象属性设置为来自数据库的值,它就可以做到这一点。也就是说,Dapper(作为一个轻型等待库)更适合这个,但是 OP 正在询问如何在没有 ORM 的情况下做到这一点。
  • @tincho87 “将 DTO 作为域的一部分是一种很好的做法”,DTO 与域对象有不同的参与方式。例如,对域对象多次执行业务操作需要参数,并且可以以 DTO 的形式传递。比如,Order.AddLine(OrderLineDto orderLine)。或者,在我的建议 1 中,您可以定义一个 StateDto 用于在特定状态下创建聚合。
【解决方案2】:

我也遇到了同样的问题,我最终使用了 Memento 模式。我不得不做出一些妥协,但最终代码完美无缺,而且维护起来绝对简单。

我解释如何设计。 在域层中,我拥有实体和纪念品类。该实体仅公开域功能,加上(这里是折衷方案)一个允许将自身导出到纪念品的额外功能。 每当我需要使用实体的内容(通常在基础设施层内)时,我都会调用从我的实体返回纪念品的函数。在这里,为了简化我的工作,我有另一个纪念品(它扩展了原始的,可以使用原始的或来自基础设施层的数据构建),它公开了一组在特定基础设施上下文中有用的功能。例如,对于持久性,我拥有构建 SQL 使用的每种类型的映射的所有函数。另一个函数获取 SQL 结果并以备忘录使用的格式存储在里面。 纪念品的构建方式很难在使用它们的环境之外构建它们。

我知道它的代码比通常有人用 ORM 编写的代码要多,而且也不使用反射等任何花哨的功能,但它可以快速测试,它正在工作,它正在做我期望的事情,我不必玩弄配置文件。这只是预编程。

【讨论】:

  • 备忘录模式非常适合这种情况,但它仍然在域和持久性之间建立了紧密的耦合。您最终将不得不在聚合中添加一些字段,只是为了持久化,这很糟糕。您应该考虑将域事件与备忘录模式配对使用来收集需要保存但不属于您的聚合的数据。
  • 并非如此。在我的实现中,没有创建或调整任何字段来支持 memento 类。此外,我有不同的纪念品(用于持久化到数据库中,用于持久化到索引等),它们扩展了默认的,并与基础设施层内的自定义函数一起使用。真实的是,我将域中的纪念品暴露给外界,但这是直接使用 sql 的功能需求。
  • @MaximeGélinas 你好!但是从数据库收集数据来填充域实体呢?这就是我在不使用任何 ORM 的情况下努力获得解决方案的地方。
  • @tincho87 这就是纪念品模式发生的地方。存储库将 db 中的数据映射到快照对象中,您可以使用 memento 模式将其加载到聚合中。这是一个例子:github.com/maximegel/stock-trader/blob/master/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-02
相关资源
最近更新 更多