65.9K
CodeProject 正在变化。 阅读更多。
Home

DDD 和 ADO.NET Entity Framework

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.60/5 (8投票s)

2009 年 6 月 1 日

CPOL

7分钟阅读

viewsIcon

52468

将领域驱动设计 (DDD) 与 ADO.NET Entity Framework 结合使用。

引言

DDD,即领域驱动设计,引起了广泛关注。有很多文章和博客文章介绍了 DDD 如何与这项或那项技术协同工作。或者如何使用 DDD 和 X 来构建应用程序?自然而然地,我也产生了兴趣,并决定了解它与 Entity Framework 的工作方式。

领域驱动设计简述

DDD 是一种创建软件的架构理念。其主要思想是,应用程序的设计应基于模型,而不是技术或其他任何东西。这不仅对开发人员来说很清晰,对该领域的专家也是如此。

领域驱动设计基于五种对象

  • 实体 (Entities):这些对象代表应用程序的核心。它们表达业务,并被客户理解。更重要的是,每个实体都有一个身份,并且在该系统内是唯一的。
  • 值对象 (Value Objects):这些对象没有身份,只描述特征。
  • 服务 (Services):服务包含不属于特定实体的逻辑。
  • 工厂 (Factories):这是一种众所周知的模式,负责创建实体。
  • 仓储 (Repositories):仓储用于分离实体及其在数据库中的表示。它们包含负责与数据库交互的代码。

这里是 维基百科上的 DDD

实体框架

简单介绍一下 Entity Framework (EF) 是什么。根据维基百科,Entity Framework 就是一个 ORM。不需要有博士学位就能明白,基于数据集的方法来处理数据库持久化并不总是有效,尤其是在大型项目中。实际上,EF 是微软的第一个 ORM(LINQ to SQL 不算)。

从框架的名称很容易猜到,EF 的核心概念是……EntityObject。EntityObjects 代表包含需要持久化信息 的对象。为了实现这一点,EF 使用三个基于 XAML 的模型和映射文件来处理实际数据。

从用户的角度来看,EF 使持久化任务变得非常简单。Visual Studio 会从数据库生成所有必需的 EntityObject 类,用户无需处理 `DataSet` 和创建 `SqlConnection`,而是直接处理代表业务对象的类。这里是 MSDN 上的 Entity Framework

DDD 实体 vs. EF EntityObjects

当人们考虑使用 DDD 和 EF 创建新应用程序时,首先想到的就是将 EF EntityObjects 用作 DDD 实体。

EntityObjects 是唯一的,并且确实有身份,这符合实体的要求。然而,它们无法与 Entity Framework 分离。根据 DDD 的方法,领域对象应仅代表业务逻辑,它们不应负责存储或显示自身或任何其他内容。这个概念也称为持久化无关性 (Persistence Ignorance),意味着领域对象不关心数据如何从数据源保存或检索。

不幸的是,EF 不是一个 PI ORM,无法让 EntityObjects 独立于框架。它们的代码是基于数据库架构和映射文件自动生成的。理论上,可以创建“真正的”实体,并将 EntityFramework 仅用作数据持久化层。然而,这是非常荒谬的,因为在这种情况下,将需要重新实现 EF 提供的所有持久化模式(工作单元、身份映射器等)。

因此,从 DDD 的角度来看,EntityFramework 充当仓储,EntityObjects 充当其集成的实体。这当然是一个可行的解决方案,但许多人更希望拥有不了解底层数据源的领域对象。

持久化无关

持久化无关性是人们争论已久的问题。有些人认为它很好,有些人认为它不错但并非总是必需。

以下是持久化无关框架不得强加给开发人员领域对象的限制:

  1. 要求继承除 `Object` 之外的指定基类
  2. 要求使用工厂方法来实例化它们
  3. 要求为属性(包括集合)使用专门的数据类型
  4. 要求实现特定的接口
  5. 要求使用特定于框架的构造函数
  6. 要求使用特定的字段或属性
  7. 禁止特定的应用程序构造
  8. 要求以提供程序特定的方言编写 RDBMS 代码,例如查询或存储过程调用

即使能够轻松更换数据库也很好,但并非所有人都需要。PI 更实际的目标是能够更轻松地模拟数据库对象,从而简化单元测试。

领域对象与持久化逻辑分离的支持者对 EF 提出的方法并不满意。他们的声音得到了听取,并承诺 EF v2 将兼容 PI。

目前,可以使用 EF 中提供的中间解决方案。这就是所谓的 IPOCO 模式。

IPOCO

POCO 是 Plain Old DLR Object 的缩写,IPOCO 是 Interface POCO 的缩写。POCO 是不包含任何复杂、特定于框架代码的对象。例如,Entity Framework 中的 EntityObjects 不是 POCO,因为它们包含很多 ORM 特定的代码。IPOCO 并非实际接口,而是一种模式,它要求实现接口而不是派生自特殊对象来使用 ORM。

Entity Framework 团队迈向 PI 的第一步是在当前版本的框架中引入了 IPOCO 兼容性。

需要由自定义类实现的三个接口:

  • IEntityWithChangeTracker 用于更改跟踪
  • IEntityWithKey 用于公开实体标识键(可选,但不实现此接口会显著降低性能)
  • IEntityWithRelationships 是具有关联的实体所必需的

如果您决定采用这种方式并使用 IPOCO 而不是 EdmGen.exe 生成的类,您应该意识到即将面临的工作量。您不仅要实现接口,还要用 EDM 特定的属性来装饰您的类,以便为框架提供必要的元数据。此外,您将失去自动属性功能的便利性,因为每个属性都需要更改跟踪功能。基本上,您在生成的 EntityObject 中看到的所有内容,都将需要自己完成。

为了更好地理解所需的编码,请参阅 MSDN 文章,其中包含所有接口的示例实现。

使用 EF 设计应用程序

EF 缺乏 PI 并不意味着它毫无价值。一点也不。它只是一种不同的方法。

我们假设我们仍然遵循 DDD,只是我们的领域对象将依赖于数据库层。这不太好,但我们希望在可预见的未来不需要更换数据库。

第一个出现的问题是,我们将领域特定逻辑放在哪里?由于 EntityObjects 是自动生成的,直接扩展它们是个糟糕的主意;相反,我们可以创建部分类来包含我们需要的所有内容。

不要忘记 EntityFramework 不仅仅是一组自动生成的类。它的 ObjectContext 将成为我们的身份映射和工作单元,因此我们的仓储将非常简单。它们将充当所有数据库交互代码的存储。这将使实体摆脱它们不需要的代码,并为我们提供在单元测试中模拟查询的机会。

我们可以再做一件事,这可能会在将来我们决定更换 ORM 时有所帮助。我们可以创建接口,并让我们的实体对象实现它们。这将使我们的生活复杂化一点,但我们会有一种“独立”的错觉,谁知道呢,也许有一天会有用……这种架构的一个例子可以在 这里 找到。

结论

如果背离 PI 思想并非至关重要,那么可以说 EF 和 DDD 可以一起使用。如果至关重要……总有其他选择。您可以通过使用 IPOCO、等待 EF v2 或使用其他 ORM(据说 NHibernate 完全兼容 PI)来实现领域对象的更高抽象级别。

参考文献

  1. Roger Jennings 著《Professional ADO.NET 3.5 with LINQ and the Entity Framework》
  2. Vijay P. Mehta 著《Pro LINQ Object Relational Mapping with C# 2008》
© . All rights reserved.