【问题标题】:Should I use DDD aggregate root repositories with EF 4.1 + LINQ?我应该将 DDD 聚合根存储库与 EF 4.1 + LINQ 一起使用吗?
【发布时间】:2011-05-18 15:23:34
【问题描述】:

我读过 DDD Evans,并且正在尝试使用 C# 和 Entity Framework 4.1 + LINQ 进行聚合根存储库设计。

但是,我担心发送到数据库的实际查询。我正在使用 SQL 2008 R2,并运行 SQL Profiler 来检查数据库响应 LINQ 代码所做的工作。

考虑一个简单的 2 实体设计,其中包含 Person 和 EmailAddress。一个人可以有零到多个电子邮件地址,一个电子邮件地址必须只有一个人。 Person 是聚合根,因此不应有电子邮件地址的存储库。电子邮件地址应从个人存储库中选择(根据 DDD Evans)。

为了比较,我确实为电子邮件地址设置了一个临时存储库。以下代码行:

var emailString = "someone@somewhere.com";
var emailEntity = _tempEmailRepository.All.SingleOrDefault(e => 
    e.Value.Equals(emailString, StringComparison.OrdinalIgnoreCase));

...根据分析器执行一个干净整洁的 SQL 查询:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]

我可以使用以下代码从人员存储库中选择电子邮件:

var emailEntity = _personRepository.All.SelectMany(p => p.Emails)
    .SingleOrDefault(e => e.Value.Equals(emailString, 
        StringComparison.OrdinalIgnoreCase))

这让我在运行时获得了相同的实体,但 SQL Profiler 中显示了不同的命令:

SELECT 
[Extent1].[Id] AS [Id],  
[Extent1].[FirstName] AS [FirstName],  
[Extent1].[LastName] AS [LastName], 
FROM [dbo].[Person] AS [Extent1]

除了上述从 Person 中选择的查询之外,还有许多“RPC:Completed”事件,一个对应于 DB 中的每个 EmailAddress 行:

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]
WHERE [Extent1].[PersonId] = 
    @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]
WHERE [Extent1].[PersonId] = 
    @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=2

我的测试数据库在 dbo.EmailAddress 中有 14 行,并且有 14 个不同的 RPC:Completed 调用,每个调用都有不同的 @EntityKeyValue1 值。

我假设这对 SQL 性能不利,因为随着 dbo.EmailAddress 表获得更多行,将在 db 上调用更多这些 RPC。在 EF 4.1 + LINQ 中使用 DDD 聚合根存储库还有其他更好的方法吗?

更新:已解决

问题是 All 属性返回了 IEnumerable<TEntity>。在将其更改为IQueryable<TEntity> 之后,LINQ 开始介入并一次性选择了整个 Person + Emails。但是,在从 All 返回 IQueryable 之前,我必须链接 .Include(p => p.Emails)。

【问题讨论】:

  • 你从All属性返回什么?
  • 好问题。当我发布这个 All 时返回一个 IEnumerable。将其更改为 IQueryable 并看到了不同之处。将发布更新。

标签: linq entity-framework-4.1 ddd-repositories


【解决方案1】:

鉴于现代 ORM 已经为您提供的抽象级别,我个人建议不要在您和您的数据库之间添加额外的抽象层。除了重新发明轮子之外,您会发现直接在服务层中使用所选的 ORM 可以让您更精细地控制查询、获取和缓存策略。

Ayende 的系列 Wages of Sin 是一个很好的资源,可以用来解释反对将规范/存储库与现代 ORM 一起使用的各种其他论点,尤其是考虑到 LINQ 已经有效地为您提供了几乎所有您可能需要的东西。

我在过去的一个项目中走的是“DDD”的路线(用引号引起来,因为它与我当时对 DDD 的理解有关)。事后看来,我意识到在公开辩论中 DDD 经常被简化为应用这些模式是一种耻辱。我掉进了那个陷阱,我希望我能帮助其他人避免它。

Repository 和 Specification 是 Infrastructure 模式。 基础设施是为了一个目的服务的,而不是一个单独的目的。在基础设施方面,我提倡严格应用重用抽象原则。快速总结一下,RAP 说,当且仅当它将被超过 2 个消费者使用并且额外的抽象层实际上实现了某些行为时,您才应该引入抽象。如果您只引入抽象以将您与某些东西(例如 ORM)分离,请非常小心,您很可能最终会得到一个泄漏的抽象。

DDD 的全部意义在于将您的领域模型与基础架构分开,并使您的领域模型尽可能具有表现力。没有证据表明不使用存储库就无法实现这一点。存储库只是用来隐藏数据访问的细节,ORM 已经这样做了。 (附带说明,考虑到 DDD 书籍的时代,我认为 ORM 的常见用途并不在当时的图片中)。现在,存储库可能对强制执行聚合根等有用。但是,我认为应该通过明确区分“读取”操作(查询)和“写入”操作(命令)来处理这一点。只有对于后者,域模型才应该是相关的,定制(和更灵活)的模型(例如 DTO 或匿名对象)通常可以更好地服务查询。

规格的情况类似。规范的预期目的是相似的。它们的力量在于构建用于查询对象的领域特定语言的元素。随着 LINQ 的出现,提供用于组合这些元素的通用规范模式的大部分“胶水”已经过时。提示:看看 Predicate Builder(

总结这篇冗长的帖子(希望不要太杂乱无章,我希望稍后会重温)帖子:

  1. 不要为基础设施而疯狂,随心所欲地构建它。
  2. 将您的领域模型用于特定领域的行为,而不是支持您的观点。
  3. 关注 DDD 中更重要的部分:使用聚合根,构建通用语言,确保与业务专家的良好沟通。

【讨论】:

  • 我上周读过这本书,感谢您提供的干净的文字/建议。我想我需要从另一个角度重读这本书
  • 所有优秀的讨论点。在我们的例子中,我认为存储库模式仍然是合理的:在某些实体中编辑数据时,原始数据将保留在数据库中。因此,任何特定实体都可以有多个修订版,其中只有 1 个是当前版本。让服务层承担这些问题将违反 SRP,因此我们应用了实体层超类型 + 存储库 (+ UoW) 来封装它们。现在的目标是使存储库尽可能地精简、轻量和少。现在要去阅读那些罪恶的工资文章......
  • 我不同意。尝试在 ORM 中为“此对象永远不应将 'name' 属性设置为 'John'”的简单案例建模。我还没有找到使用 EF 或 NHibernate 或其他任何东西的合理解决方案。
猜你喜欢
  • 1970-01-01
  • 2012-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-08
  • 1970-01-01
相关资源
最近更新 更多