【问题标题】:Entity Framework, Navigation Properties, and the Repository Pattern实体框架、导航属性和存储库模式
【发布时间】:2012-03-24 07:22:40
【问题描述】:

我正在努力找出实体框架和存储库模式的理想实现。我使用的是 Entity Framework 4.3 代码优先,但我似乎无法很好地正确使用实体框架。

我喜欢 EF 为表格带来的东西,例如跟踪的实体、延迟加载、导航属性等。但据我所知,其中一些与存储库模式不兼容。让我们看几个例子,也许你们可以让我明白。

通用存储库与非通用存储库

我对通用存储库的最初印象是我不喜欢它,因为我不需要为每个实体提供完全相同的功能。例如,我有一个在数据库中存储简单变量(键/值对)的存储库。我不需要 Add 或 Delete 方法,因为这些是静态变量。我只需要一个 Update 方法和一个 Get 方法。通用存储库似乎不是很健壮,并且不允许在数据层中使用太多自定义代码。我也讨厌通用存储库返回IQueryable<T>,因为它使上层能够直接针对数据存储编写表达式,并且上层必须假设正在使用的数据访问技术正确实现了 IQueryable 以便查询数据库而不是将所有内容都拉入内存并从那里查询。

似乎通用存储库,尤其是返回 IQueryable 的存储库,并没有真正遵守良好的关注点分离。也许你们可以帮我清理一下,但现在我使用的是显式命名的存储库,并且只返回 IEnumerable 或 IList。

导航属性

我喜欢导航属性的概念,但在实现存储库模式时似乎很少使用它们。例如,我有一个具有名为“别名”的导航属性的用户。如果我想为用户添加别名,通过导航属性添加它会非常容易。

myUser.Aliases.Add(new Alias { Name="cls", Value="ClearScreen" });

但是我应该在哪里打电话给dbContext.SaveChanges()?我将myUser 传递给了我,我使用了导航属性来避免将IAliasRepository 注入我所在的类中。但是我现在无法将我的新别名保存到数据库中,因为我的上层是不知道实体框架。我现在必须注入我的IAliasRepository,这样我才能全部_aliasRepository.SaveChanges()。好吧,现在感觉完全是浪费。我觉得我应该改用_aliasRepository.AddAlias(newAlias),因为无论如何我都必须注入存储库。

自我跟踪实体

自我跟踪实体非常棒,但它们不适合您试图向应用程序的其余部分隐藏数据访问层详细信息的应用程序。例如,如果我正在编写存储库并且完全不知道他们会使用 EF,那么我肯定会添加一个 Update(Entity entity) 方法。但是,在 EF 中您不需要这样做,因为您可以简单地对实体进行更改,然后调用 SaveChanges()。该实体跟踪所有被修改的内容并将这些更改保存到数据库中。

var myEntity = _entityRepository.GetEntity("some unique ID");
myEntity.SomeProperty = "new value";
_entityRepository.SaveChanges();

如果我不知道 EF 不需要这些更新方法,我会删除这些更新方法。这使得重构变得更加困难,因为我可能不得不返回并添加适当的更新方法。我唯一的其他选择是无论如何都包含这些方法,然后在我实现我的存储库时对它们不做任何事情。

public void UpdateEntity(Entity entity)
{
    // Do nothing. EF is tracking changes and they will be persisted when
    // SaveChanges() is called.
}

所以我的代码看起来像这样,即使它完全没有必要。

var myEntity = _entityRepository.GetEntity("some unique ID");
myEntity.SomeProperty = "new value";
_entityRepository.UpdateEntity(myEntity);
_entityRepository.SaveChanges();

如果我只是想保持适当的关注点分离以便以后轻松重构,我想拥有一个空方法并不可怕,但这样做仍然感觉很有趣。

保持 DbContext 同步

此模式的另一个奇怪之处是您必须格外小心您的 DbContext。它的同一个实例需要注入到所有存储库中。否则,如果您将实体从一个存储库中拉出并尝试将它们与另一个存储库中的实体相关联,那么它们将无法很好地结合在一起,因为它们来自不同的 DbContext 实例。 IoC 容器使这更容易控制,但对于刚开始使用 EF 的开发人员来说,这是一个奇怪的问题。这里并不是一个真正的问题,而只是实体框架和存储库模式的另一个奇怪之处。

什么是使用 EF 的存储库模式的正确实现?你如何克服这些障碍?

【问题讨论】:

  • 嗯,我注意到的一件事是,您似乎缺少工作单元模式。另外,我喜欢您对 IQueryable. 的评论
  • @Hexxagonal 实际上我什至没有听说过“工作单元”模式。需要详细说明吗?
  • @Hexxagonal 我不久前写了一篇文章,详细介绍了为什么我认为 IQueryable 是一种糟糕的类型,如果您对一些简单的阅读感兴趣,那么从您的数据层返回:codetunnel.com/blog/post/103/…
  • 对于 EF,我使用工作单元来包装上下文并保持所有存储库协同工作。此外,抽象出 EF 数据访问。当您混合和匹配数据库时,它尤其有用。谢谢你的博文,我去看看。
  • 嗯,我想我真的对“工作单元”是什么感到迷茫。我得再用谷歌搜索一下。

标签: c# .net linq entity-framework entity-framework-4


【解决方案1】:

通用存储库与非通用存储库

通用存储库不是一种模式。 Generic repository is just a wrapper。作为特定存储库的基类,它在某些特殊情况下可能很有用,但在大多数情况下,它只是被过度使用和过度炒作。

导航属性

存储库本身应与聚合根一起使用。聚合根是多个相关实体的聚合,其中您只有主体的存储库,因为没有父级就不能存在依赖关系。存储库本身负责加载和持久化聚合中的所有实体类型。

即使有聚合根,你也会遇到一些挑战。例如如何处理多对多关系?多对多关系总是代表没有真正的主体或依赖实体=它们不在聚合中的场景。如果您仅通过导航属性设置这两个实体之间的关系,它仍然可以,但 EF 还允许您通过导航属性创建相关实体,并且它以某种方式违反了存储库的目的。您可以强制您的存储库测试关系的存在,但它可能会导致大量额外的查询,因此您很可能会将其保留为您的实现的泄漏抽象。

自我跟踪实体

根据您的代码,我认为您将 self-tracking entities 与附加实体混淆了。您所描述的是附加实体和分离实体之间的区别。如果您想在单个代码中同时支持这两种情况,那么您的 UpdateEntity 方法是有意义的,因为您必须测试是否附加了实体,如果没有附加,则附加它 + 设置状态。

保持 DbContext 同步

这是您缺少工作单元的地方。存储库本身仅包装查询并将实体存储到上下文中,但工作单元处理上下文创建/撤销和对数据库的持久更改。例如,您可以使用DbSet(EF 的存储库实现)和DbContext(EF 的工作单元实现)。

【讨论】:

  • 好的,所以我想我需要一名技术翻译人员来回复您对导航属性的回复。 “聚合根”是什么意思?同样对于 DbContext,我不太确定您所说的“工作单元”是什么意思。
  • 也许更好的问题是,“您将如何更改我现有的存储库模式?”目前我有大约 20 个不同的存储库(每个实体一个),每个存储库都有 Ninject 注入的 DbContext。对于这个特定的应用程序,切换数据访问实现是一种非常现实的可能性,所以我确实认为我想要存储库,但也许你有更好的主意?
  • 如果您愿意,您可以在我的项目本身中查看我对存储库模式的实现。 github.com/Chevex/CT-Terminal。我真的很好奇你的想法。
  • 经过深思熟虑和谷歌搜索,我想我终于明白了你试图告诉我的关于“工作单元”的内容我仍然不确定你对导航属性的意思,但是工作单元想法很有意义。
  • @AlexFord:要了解什么是“聚合根”,请尝试研究“领域驱动设计”>> DDD。
猜你喜欢
  • 2018-07-04
  • 2010-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多