简体   繁体   English

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

[英]Entity Framework, Navigation Properties, and the Repository Pattern

I am struggling to figure out the ideal implementation of Entity Framework and the repository pattern. 我正在努力找出实体框架和存储库模式的理想实现。 I'm using Entity Framework 4.3 code-first and I just can't seem to wrap my head around good proper usage of entity framework. 我正在使用Entity Framework 4.3代码优先,而我似乎无法完全理解实体框架的正确用法。

I love the things EF brings to the table such as tracked entities, lazy loading, navigation properties, etc. But some of these don't play nice with the repository pattern as I understand it. 我喜欢EF给桌面带来的东西,比如跟踪实体,延迟加载,导航属性等等。但是根据我的理解,其中一些对于存储库模式并不好。 Let's look at a few examples and maybe you guys can set me straight. 让我们看一些例子,也许你们可以让我直截了当。

Generic Repository vs Non-generic Repository 通用存储库与非通用存储库

My initial impression of the generic repository is that I don't like it because I don't need the exact same functionality for every entity. 我对通用存储库的初步印象是我不喜欢它,因为我不需要为每个实体提供完全相同的功能。 For example, I have a repository that stores simple variables (key/value pairs) in the database. 例如,我有一个存储库,用于在数据库中存储简单变量(键/值对)。 I don't need an Add or Delete method because these are static variables. 我不需要Add或Delete方法,因为这些是静态变量。 I only need an Update method and a Get method. 我只需要一个Update方法和一个Get方法。 The generic repository just doesn't seem very robust and doesn't allow for much custom code in the data layer. 通用存储库似乎不是很健壮,并且不允许数据层中有太多自定义代码。 I also hate when generic repositories return IQueryable<T> because it gives the upper layers the ability to write expressions directly against the data store, and the upper layers have to assume that the data access technology being used properly implements IQueryable so that it's querying the database and not pulling everything into memory and querying it from there. 我也讨厌通用存储库返回IQueryable<T>因为它使上层能够直接针对数据存储编写表达式,而上层必须假设正确使用的数据访问技术实现IQueryable,以便它查询数据库,而不是将所有内容都拉入内存并从那里查询。

It just seems like generic repositories, especially ones that return IQueryable, don't really adhere to good separation of concerns. 它看起来像通用存储库,特别是返回IQueryable的存储库,并不真正坚持良好的关注点分离。 Maybe you guys can clear that one up for me, but right now I'm using explicitly named repositories and only returning IEnumerable or IList. 也许你们可以为我清除那个,但是现在我正在使用显式命名的存储库,只返回IEnumerable或IList。

Navigation Properties 导航属性

I love the concept of navigation properties, but it seems like I rarely get to use them when implementing the repository pattern. 我喜欢导航属性的概念,但在实现存储库模式时,我似乎很少使用它们。 For instance, I have a user with a navigation property called "Aliases". 例如,我有一个名为“Aliases”的导航属性的用户。 If I want to add an Alias for a user it would be super easy to add it via the navigation property. 如果我想为用户添加别名,通过导航属性添加它会非常容易。

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

But then where do I call dbContext.SaveChanges() ? 但那我在哪里调用dbContext.SaveChanges() I had myUser passed to me and I used the navigation property to avoid having to inject my IAliasRepository into the class I'm in. However I now have no way to persist my new alias to the database because my upper layers are unaware of Entity Framework. 我将myUser传递给了我,我使用了导航属性,以避免将我的IAliasRepository注入到我IAliasRepository的类中。但是我现在无法将我的新别名保留到数据库中,因为我的上层不知道实体框架。 I now have to inject my IAliasRepository anyway just so I can all _aliasRepository.SaveChanges() . 我现在必须注入我的IAliasRepository ,所以我可以_aliasRepository.SaveChanges() Well now that feels like a complete waste. 那么现在感觉就像完全浪费。 I feel like I should have just used _aliasRepository.AddAlias(newAlias) instead since I have to have the repository injected anyway. 我觉得我应该使用_aliasRepository.AddAlias(newAlias)因为我必须要注入存储库。

Self-Tracking Entities 自我追踪实体

Self-tracking entities are awesome but they don't lend themselves well to applications where you're trying to hide the data access layer details from the rest of the app. 自我跟踪实体非常棒,但它们并不适合您试图隐藏应用程序其余部分的数据访问层详细信息的应用程序。 For instance, if I were writing repositories and being totally ignorant that they would be using EF then I would most definitely add an Update(Entity entity) method. 例如,如果我正在编写存储库并且完全不知道他们将使用EF,那么我肯定会添加一个Update(Entity entity)方法。 However, in EF you don't need to do that because you can simply make changes to an entity and then call SaveChanges() . 但是,在EF中,您不需要这样做,因为您只需对实体进行更改,然后调用SaveChanges() The entity tracks everything that was modified and persists those changes to the database. 该实体跟踪已修改的所有内容,并将这些更改持久保存到数据库中。

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

This causes me to eliminate my update methods that I would have included had I not been aware that EF doesn't need them. 这导致我消除了我将包含的更新方法,如果我不知道EF不需要它们。 This makes re-factoring harder down the road because I may have to go back and add proper update methods. 这使得重新分解更加困难,因为我可能必须返回并添加适当的更新方法。 My only other option would be to include the methods anyway and then just do nothing with them when I implement my repositories. 我唯一的另一种选择是无论如何都要包含这些方法,然后在实现我的存储库时对它们不做任何处理。

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

So my code would look like this, even though it's completely unnecessary. 所以我的代码看起来像这样,即使它完全没必要。

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

I suppose having an empty method isn't terrible if I'm just trying to maintain proper separation of concerns for easy refactoring later, but it still feels funny to do that. 我想假设一个空方法并不可怕,如果我只是为了保持适当的关注点分离以便以后进行简单的重构,但这样做仍然感觉很有趣。

Keeping DbContext in sync 保持DbContext同步

Another weird quirk of this pattern is that you have to be extra careful with your DbContext. 这种模式的另一个奇怪的怪癖是你必须要特别小心你的DbContext。 The same instance of it needs to be injected into all repositories. 需要将相同的实例注入所有存储库。 Otherwise if you pull entities out of one repository and try to associate them with entities from another repository then they won't play nice together because they are from different instances of DbContext. 否则,如果您将实体从一个存储库中拉出来并尝试将它们与另一个存储库中的实体相关联,那么它们将不能很好地协同工作,因为它们来自不同的DbContext实例。 IoC containers make this easier to control, but it's an odd issue for developers just beginning with EF. IoC容器使这更容易控制,但对于刚开始使用EF的开发人员来说,这是一个奇怪的问题。 Not really a problem here so much as just another oddity with Entity Framework and the repository pattern. 这里不是一个真正的问题,只是与Entity Framework和存储库模式的另一个奇怪之处。

What is the proper implementation of the repository pattern with EF? 使用EF正确实现存储库模式是什么? How do you overcome these hurdles? 你如何克服这些障碍?

Generic Repository vs Non-generic Repository 通用存储库与非通用存储库

Generic repository is not a pattern. 通用存储库不是模式。 Generic repository is just a wrapper . 通用存储库只是一个包装器 It can be useful for some special scenarios as a base class for specific repositories but in most cases it is just over used and over hyped nonsense. 它对于某些特殊情况作为特定存储库的基类非常有用,但在大多数情况下它只是过度使用而且过于夸张的废话。

Navigation properties 导航属性

Repository itself should be used with aggregate root. 存储库本身应与聚合根一起使用。 The aggregate root is aggregation of multiple related entities where you have repository only for principal because dependencies cannot exist without parent. 聚合根是多个相关实体的聚合,其中您只有主体的存储库,因为没有父项,依赖关系不能存在。 Repository itself handles loading and persisting all entity types in aggregation. 存储库本身处理聚合中的所有实体类型的加载和持久化。

Even with aggregate roots you will end with some challenges. 即使有了根源,你也会遇到一些挑战。 For example how to handle many-to-many relations? 例如,如何处理多对多关系? Many to many relation always represent scenario where there is no real principal or dependent entity = they are not in aggregation. 多对多关系总是表示没有真正的主体或依赖实体的情况=它们不在聚合中。 If you only set relation between these two entities through navigation property it is still OK but EF also allows you creating related entity through navigation property and it somehow violates repository purpose. 如果你只通过导航属性设置这两个实体之间的关系它仍然可以,但EF也允许你通过导航属性创建相关的实体,它不知何故违反了存储库的目的。 You can enforce your repository to test existence of relations but it can cause a lot of additional queries so you will most probably leave it as leaky abstraction of your implementation. 您可以强制执行存储库以测试关系的存在,但它可能会导致许多其他查询,因此您很可能将其作为实现的漏洞抽象。

Self-Tracking entities 自我跟踪实体

From your code I think you confused self-tracking entities with attached entities. 从您的代码中我认为您将自我跟踪实体与附加实体混淆。 What you describe is difference between attached and detached entities. 您描述的是附加实体和分离实体之间的区别。 If you want to support both scenarios in single code your UpdateEntity method has a meaning because you must test if entity is attached and attach it + set state if not. 如果要在单个代码中支持这两种方案,则UpdateEntity方法具有意义,因为您必须测试是否附加了实体并附加了它+如果没有则附加设置状态。

Keeping DbContext in sync 保持DbContext同步

This is where you are missing unit of work. 这是你缺少工作单位的地方。 Repository itself wraps only queries and storing entities into context but unit of work handles context creation / retireval and persisting changes to database. 存储库本身仅包装查询并将实体存储到上下文中,但工作单元处理上下文创建/退役和持久更改数据库。 As example you can take DbSet (EF's implementation of repository) and DbContext (EF's implementation of unit of work). 例如,您可以使用DbSet (EF的存储库实现)和DbContext (EF的工作单元实现)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM