实体框架、导航属性和存储库模式

本文关键字:存储 模式 属性 框架 导航 实体 | 更新日期: 2023-09-27 18:26:50

我正在努力找出实体框架和存储库模式的理想实现。我首先使用实体框架4.3代码,我似乎无法正确使用实体框架。

我喜欢EF带来的东西,比如跟踪实体、延迟加载、导航属性等。但据我所知,其中一些对存储库模式不太好。让我们看几个例子,也许你们可以纠正我的错误。

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

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

似乎通用存储库,尤其是那些返回IQueryable的存储库,并没有真正遵守良好的关注点分离。也许你们可以帮我解决这个问题,但现在我使用的是显式命名的存储库,只返回IEnumerable或IList。

导航属性

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

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

但我在哪里叫dbContext.SaveChanges()?我已经将myUser传递给了我,我使用了navigation属性来避免将我的IAliasRepository注入到我所在的类中。然而,我现在无法将我的新别名持久化到数据库中,因为我的上层不知道Entity Framework。我现在必须注入我的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的存储库模式的正确实现是什么?你是如何克服这些障碍的?

实体框架、导航属性和存储库模式

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

通用存储库不是一种模式。通用存储库只是一个包装器。作为特定存储库的基类,它在某些特殊场景中可能很有用,但在大多数情况下,它只是被过度使用和炒作。

导航属性

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

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

自跟踪实体

从您的代码中,我认为您混淆了自跟踪实体和附加实体。您所描述的是附着实体和分离实体之间的区别。如果您想在单个代码中支持这两种场景,那么UpdateEntity方法就有意义了,因为您必须测试实体是否已附加,如果未附加,则必须附加实体+设置状态。

保持DbContext同步

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