我正在努力找出实体框架和存储库模式的理想实现。我正在使用Entity Framework 4.3代码优先,而我似乎无法完全理解实体框架的正确用法。
我喜欢EF给桌面带来的东西,比如跟踪实体,延迟加载,导航属性等等。但是根据我的理解,其中一些不能很好地使用存储库模式。让我们看一些例子,也许你们可以让我直截了当。
我对通用存储库的初步印象是我不喜欢它,因为我不需要为每个实体提供完全相同的功能。例如,我有一个存储库,用于在数据库中存储简单变量(键/值对)。我不需要Add或Delete方法,因为这些是静态变量。我只需要一个Update方法和一个Get方法。通用存储库似乎不是很健壮,并且不允许数据层中有太多自定义代码。我也讨厌通用存储库返回IQueryable<T>
,因为它使上层能够直接针对数据存储编写表达式,而上层必须假设正确使用的数据访问技术实现IQueryable以便它查询数据库,而不是将所有内容都拉入内存并从那里查询。
它看起来像通用存储库,尤其是返回IQueryable的存储库,并不真正坚持良好的关注点分离。也许你们可以为我清除那个,但是现在我正在使用显式命名的存储库,只返回IEnumerable或IList。
我喜欢导航属性的概念,但在实现存储库模式时,我似乎很少使用它们。例如,我有一个名为“Aliases”的导航属性的用户。如果我想为用户添加别名,通过导航属性添加它会非常容易。
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实例。 IoC容器使这更容易控制,但对于刚开始使用EF的开发人员来说,这是一个奇怪的问题。这不是一个真正的问题,只是与实体框架和存储库模式的另一个奇怪。
使用EF正确实现存储库模式是什么?你如何克服这些障碍?
答案 0 :(得分:14)
通用存储库与非通用存储库
通用存储库不是模式。 Generic repository is just a wrapper。对于某些特殊情况,它可以作为特定存储库的基类使用,但在大多数情况下,它只是过度使用而且大肆宣传。
导航属性
存储库本身应与聚合根一起使用。聚合根是多个相关实体的聚合,其中您只有主体的存储库,因为没有父项,依赖关系不能存在。存储库本身处理加载和持久化聚合中的所有实体类型。
即使有了根源,你也会遇到一些挑战。例如,如何处理多对多关系?多对多关系总是表示没有真正的主体或依赖实体的情况=它们不在聚合中。如果你只通过导航属性设置这两个实体之间的关系它仍然可以,但EF也允许你通过导航属性创建相关的实体,它不知何故违反了存储库的目的。您可以强制执行存储库以测试关系的存在,但它可能会导致许多其他查询,因此您很可能将其作为实现的漏洞抽象。
自我跟踪实体
从您的代码中我认为您将self-tracking entities与附加实体混淆了。您描述的是附加实体和分离实体之间的区别。如果您希望在单个代码中支持这两种方案,则UpdateEntity
方法具有意义,因为您必须测试是否附加了实体并附加了它+如果没有,则设置状态。
保持DbContext同步
这是您缺少工作单位的地方。存储库本身仅包装查询并将实体存储到上下文中,但工作单元处理上下文创建/退役和持久更改数据库。例如,您可以使用DbSet
(EF的存储库实现)和DbContext
(EF的工作单元实现)。