2011-05-18 69 views
5

我读过DDD Evans,并且正在使用C#和Entity Framework 4.1 + LINQ试验一个聚合根存储库设计。我应该在EF 4.1 + LINQ中使用DDD聚合根存储库吗?

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

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

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

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 emailString = "[email protected]"; 
var emailEntity = _tempEmailRepository.All.SingleOrDefault(e => 
    e.Value.Equals(emailString, StringComparison.OrdinalIgnoreCase)); 

...根据探查执行一个干净的SQL查询:

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

这让我在运行相同的实体,但不同的命令显示了在SQL事件探查器:

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

除了从人选择上面的查询,有许多的“RPC:已完成”事件,一个用于在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 

我的测试分贝有14行在dbo.EmailAddress中,并且有14个不同的RPC:已完成的调用,每个都有不同的@ EntityKeyValue1值。

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

更新:解决

的问题是,所有的财产返回一个IEnumerable<TEntity>。在这改为IQueryable<TEntity>之后,LINQ一下子就进入并选择了整个Person + Emails。但是,我必须链接.Include(p => p.Emails)才能从All返回IQueryable。

+2

你从'All'属性返回什么? – 2011-05-18 15:26:36

+0

好问题。当我发布这个所有返回一个IEnumerable 。将其更改为IQueryable并查看其差异。将发布更新。 – danludwig 2011-05-18 19:23:17

回答

12

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

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

我在过去的项目中走过了“DDD”的路线(在引号中,因为它当时对DDD的理解是必然的)。事后看来,我认识到,在公开辩论中,DDD通常被缩减为应用这些模式是一种耻辱。我陷入了陷阱,我希望我能帮助别人避免它。

存储库和规格是基础设施模式。 基础设施是为了达到目的,而不是为了自己的目的。谈到基础架构,我主张严格应用重用抽象原则。为了快速总结,RAP表示,如果并且只有当它将被超过2个消费者使用并且额外的抽象层实际上实现某些行为,则应该引入抽象。如果你只引入一个抽象来将你与某些东西(比如ORM)分离开来,那么要非常小心,这很可能最终会导致漏洞的抽象。

DDD的重点在于让您的域模型与您的基础架构分开,并使您的域模型尽可能具有表现力。没有证据表明,如果不使用存储库,这是无法实现的。存储库仅仅是为了隐藏数据访问的细节,ORM已经有了。 (在一个侧面说明中,考虑到DDD书的年龄,我认为ORM的常见用途不在当时)。现在,存储库对强制执行聚合根等是有用的。但是,我认为应该通过在“读取”操作(查询)和“写入”操作(命令)之间作出明确的区分来对待它。只有后者的领域模型应该是相关的,查询往往更适合于量身定制(并且更灵活)的模型(比如DTO或者匿名对象)。

规格的情况类似。规格的预期目的是相似的。他们的力量在于构建用于查询对象的领域特定语言的元素。随着LINQ的出现,大部分提供用于组合这些元素的泛型规范模式的“粘合剂”已经过时。提示:看看Predicate Builder(C#的50行代码),很可能你需要实现规范。

总结这个漫长的(希望不是太混乱,我将重新后来我希望)职位:

  1. 不要去狂爱的基础设施,构建它,当您去。
  2. 将您的域模型用于域特定行为,而不是用于支持您的视图。
  3. 专注于DDD更重要的部分:使用聚合根,建立你无处不在的语言,确保与业务专家的良好沟通。
+0

上周我读过这本书,感谢您提供干净的单词/建议。我想我需要从另一个角度重读这本书 – Khh 2011-05-18 21:07:40

+0

所有优秀的讨论要点。在我们的案例中,我认为存储库模式仍然是合理的:在某些实体中编辑数据时,原始数据将保存在数据库中。因此,任何特定的实体都可以有多个修订版,其中只有一个是当前版本。这将会违反SRP来为服务层提供这些担忧,所以我们应用了实体层超类型+存储库(+ UoW)来封装它们。现在的目标是保持存储库尽可能薄,轻,少。去读这些罪的工资现在... – danludwig 2011-05-20 13:22:39

+0

我不得不不同意。尝试在ORM中建模一个简单的“这个对象不应该将'name''属性设置为'John'”的案例。我还没有找到EF或NHibernate或其他任何可行的解决方案。 – drozzy 2013-07-29 16:44:31