35

我仍然有一些與存儲庫模式混淆。我想要使​​用這種模式的主要原因是爲了避免從域中調用EF 4.1特定的數據訪問操作。我寧願從IRepository接口調用通用的CRUD操作。這將使測試變得更容易,如果將來我必須更改數據訪問框架,我將能夠在不重構大量代碼的情況下進行測試。存儲庫模式與實體框架4.1和父/子關係

這裏是我的情況爲例:

我在數據庫中有3個表:GroupPersonGroupPersonMapGroupPersonMap是一個鏈接表,只包含GroupPerson主鍵。我使用VS 2010設計器創建了3張表的EF模型。 EF足夠聰明地假設GroupPersonMap是一個鏈接表,所以它不會在設計器中顯示它。我想使用現有的域對象而不是EF的生成類,因此我關閉了模型的代碼生成。

我的EF模型匹配現有的類如下:

public class Group 
{ 
    public int GroupId { get; set; } 
    public string Name { get; set; } 

    public virtual ICollection<Person> People { get; set; } 
} 

public class Person 
{ 
    public int PersonId {get; set; } 
    public string FirstName { get; set; } 

    public virtual ICollection<Group> Groups { get; set; } 
} 

我有一個通用的存儲庫接口,像這樣:

public interface IRepository<T> where T: class 
{ 
    IQueryable<T> GetAll(); 
    T Add(T entity); 
    T Update(T entity); 
    void Delete(T entity); 
    void Save() 
} 

和通用EF庫:

public class EF4Repository<T> : IRepository<T> where T: class 
{ 
    public DbContext Context { get; private set; } 
    private DbSet<T> _dbSet; 

    public EF4Repository(string connectionString) 
    { 
     Context = new DbContext(connectionString); 
     _dbSet = Context.Set<T>(); 
    } 

    public EF4Repository(DbContext context) 
    { 
     Context = context; 
     _dbSet = Context.Set<T>(); 
    } 

    public IQueryable<T> GetAll() 
    { 
     // code 
    } 

    public T Insert(T entity) 
    { 
     // code 
    } 

    public T Update(T entity) 
    { 
     Context.Entry(entity).State = System.Data.EntityState.Modified; 
     Context.SaveChanges(); 
    } 

    public void Delete(T entity) 
    { 
     // code 
    } 

    public void Save() 
    { 
     // code 
    } 
} 

現在假設我只想將現有的Group映射到現有的Person。我會做一些類似如下:

 EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString"); 
     EFRepository<Person> personRepository = new EFRepository<Person>("name=connString"); 

     var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First(); 
     var person = personRepository.GetAll().Where(p => p.PersonId == 2).First(); 

     group.People.Add(person); 
     groupRepository.Update(group); 

但是,這並不工作,因爲EF認爲Person是新的,並會嘗試重新INSERTPerson到數據庫中,這將導致一個主鍵約束錯誤。我必須使用DbSetAttach方法來告訴EF Person已經存在於數據庫中,因此只需在GroupPersonMap表中創建GroupPerson之間的映射。

因此,爲了連接Person的方面,我現在必須的Attach方法添加到我的IRepository:

public interface IRepository<T> where T: class 
{ 
    // existing methods 
    T Attach(T entity); 
} 

要解決主鍵約束錯誤:

EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString"); 
EFRepository<Person> personRepository = new EFRepository<Person>(groupRepository.Context); 

var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First(); 
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First(); 

personRepository.Attach(person); 
group.People.Add(person); 
groupRepository.Update(group); 

固定。現在,我必須處理另一個問題,其中每次創建組/人員映射時,都會在數據庫中更新Group。這是因爲在我的EFRepository.Update()方法中,實體狀態明確設置爲Modified'. I must set the Group's state to未更改so the組表未被修改。

爲了解決這個問題,我必須添加某種Update過載到我的IRepository不更新根實體,或Group的,在這種情況下:

public interface IRepository<T> where T: class 
{ 
    // existing methods 
    T Update(T entity, bool updateRootEntity); 
} 

更新方法的EF4 implentation看起來會是像這樣:

T Update(T entity, bool updateRootEntity) 
{ 
    if (updateRootEntity) 
     Context.Entry(entity).State = System.Data.EntityState.Modified; 
    else 
     Context.Entry(entity).State = System.Data.EntityState.Unchanged; 

    Context.SaveChanges(); 
} 

我的問題是:我接近這個正確的方式?當我開始使用EF和存儲庫模式時,My Repository開始看起來以EF爲中心。感謝您閱讀這篇長文章

回答

66

The primary reason why I want to use this pattern is to avoid calling EF 4.1 specific data access operations from the domain. I'd rather call generic CRUD operations from a IRepository interface. This will make testing easier

沒有它will not make your testing easierYou exposed IQueryable因此您的存儲庫is not unit testable

if I ever have to change the data access framework in the future, I will be able to do so without refactoring a lot of code.

不,你必須要變了很多的代碼,因爲你暴露IQueryable因爲EF/ORM是漏水的抽象 - 你的高層預計,一些神奇的行爲發生你的ORM內(例如延遲加載)。這也是存儲庫最奇怪的原因之一。現在只需選擇正確的技術並使用它來獲得它的賭注。如果稍後必須更改它,則意味着要麼是you did a mistake and chose the wrong one or requirements have changed--無論是哪種情況,這都將是很多工作。

But this doesn't work because EF thinks Person is new, and will try to re-INSERT the Person into the database which will cause a primary key constraint error.

是的,因爲您正在爲每個存儲庫使用新的上下文=這是錯誤的方法。存儲庫必須共享上下文。您的第二個解決方案也不正確,因爲您將EF依賴項放回應用程序 - 存儲庫正在公開上下文。這通常通過第二種模式 - 工作單元來解決。 Unit of work wraps the context和工作單元形成原子更改集 - SaveChanges必須在工作單元上公開,以提交所有相關存儲庫完成的更改。

Now I have an issue with the Group being UPDATE'd in the database every time I want to create a Group/Person map.

你爲什麼改變狀態?您從存儲庫中接收到實體,因此直到您將其分離後,沒有理由調用Attach並手動更改狀態。這一切應該在附屬實體上自動發生。只需致電SaveChanges即可。如果你正在使用分離的實體,那麼you must correctly set state for every entity和關係,所以在這種情況下,你確實需要一些邏輯或更新重載來處理所有場景。

Am I approaching this the right way? My Repository is starting to look EF centric as I start to work with EF and the repository pattern.

我不這麼認爲。首先你不使用聚合根。如果你這樣做,你會立即發現,generic repository是不適合的。聚合根的存儲庫爲每個聚合根擁有特定的方法來處理由根聚合的關係。 Group不是Person集合的一部分,但GroupPersonMap應該是這樣的,您的Person存儲庫應該具有特定的方法來處理從人員添加和刪除組(但不創建或刪除組本身)。 Imo通用存儲庫是redundant layer

+4

+1很好的答案 –

+1

+1非常豐富的 –

+0

+1爲實體框架+工作單元。存儲庫需要共享相同的dbcontext。我敢打賭,這是我的問題。 – Jhayes2118