实体框架 4.1 - 我是否应该将 DDD 聚合根存储库与 EF 4.1 + LINQ 一起使用



我已经阅读了DDD Evans,并且我正在尝试使用C#和实体框架4.1 + LINQ进行聚合根存储库设计。

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

考虑一个简单的 2 实体设计,其中包含人员和电子邮件地址。一个人可以有零到多个电子邮件地址,一个电子邮件地址必须只有一个人。人员是聚合根,因此不应有电子邮件地址的存储库。电子邮件地址应从个人存储库中选择(根据 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 事件探查器中显示了不同的命令:

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

除了上述从"人员"中选择的查询之外,还有许多"RPC:已完成"事件,每个事件对应数据库中的每个电子邮件地址行:

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 中有 14 行。电子邮件地址,并且有 14 个不同的 RPC:已完成调用,每个呼叫具有不同的@EntityKeyValue1值。

我假设这对 SQL 性能不利,因为作为 dbo。电子邮件地址表获得更多行,这些 RPC 中的更多将在数据库上调用。是否有其他更好的方法将 DDD 聚合根存储库与 EF 4.1 + LINQ 配合使用?

更新:已解决

问题是所有属性返回IEnumerable<TEntity>。在更改为IQueryable<TEntity>后,LINQ启动并一次性选择了整个"人员+电子邮件"。但是,我不得不链接.包括(p => p.Email),然后从所有人返回IQueryable。

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

Ayende的《罪恶的工资》系列是一个很好的资源,可以很好地讨论其他各种反对将规范/存储库与现代ORM一起使用的论点,特别是考虑到LINQ实际上已经为你提供了几乎所有可能需要的东西。

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

存储库和规范是基础结构模式。基础设施的存在是为了服务一个目的,而不是为了单独成为一个目的。在基础设施方面,我主张严格应用重用抽象原则。为了给出一个快速的总结,RAP说你应该引入一个抽象,当且仅当它将被超过2个消费者使用,并且额外的抽象层实际上实现了某些行为。如果你只引入一个抽象来将你与某些东西(比如ORM)解耦,要非常小心,很可能你最终会得到一个泄漏的抽象。

DDD 的全部意义在于将域模型与基础结构分开,并使域模型尽可能富有表现力。没有证据表明,如果不使用存储库,就无法做到这一点。存储库只是为了隐藏数据访问的详细信息,ORM已经这样做了。(附带说明一下,考虑到DDD书的年龄,我认为当时ORM的常见用途并不常见)。现在,存储库对于强制实施聚合根等可能很有用。但是,我认为应该通过明确区分"读取"操作(查询)和"写入"操作(命令)来处理这个问题。只有对于后者,域模型才应该是相关的,查询通常由定制(且更灵活)的模型(例如 DTO 或匿名对象)更好地提供服务。

规范的情况类似。规范的预期目的类似。它们的强大之处在于构建用于查询对象的域特定语言的元素。随着 LINQ 的出现,通用规范模式提供的用于组合这些元素的大部分"粘合"已经过时。提示:看看谓词生成器(<50行C#),它可能是实现规范所需要的全部。

总结一下这个冗长的(希望不要太杂乱无章,我希望我稍后会重新审视)帖子:

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

相关内容

  • 没有找到相关文章

最新更新