2012-06-07 61 views
59

我最近學會了ASP.NET MVC(我喜歡它)。我正在與一家使用依賴注入在每個請求中加載Repository實例的公司合作,並且我熟悉使用該存儲庫。ASP.NET MVC的最佳存儲庫模式

但現在我正在寫幾個我自己的MVC應用程序。我並不完全瞭解我公司使用的存儲庫的方式和原因,我試圖確定實現數據訪問的最佳方法。

我正在使用C#和實體框架(與所有最新版本)。

我看到了三種處理數據訪問的一般方法。

  1. 每次訪問數據時,使用語句中的常規數據庫上下文。這很簡單,它工作正常。但是,如果兩個位置需要在一個請求中讀取相同的數據,則必須讀取兩次數據。 (每個請求使用一個存儲庫,兩個地方使用相同的實例,我理解第二次讀取將簡單地從第一次讀取返回數據。)

  2. 典型的repository pattern。由於我不明白的原因,這種典型的模式涉及到爲數據庫中使用的每個表創建一個包裝類。這對我來說似乎是錯誤的。實際上,因爲它們也作爲接口來實現,所以我在技術上爲每個表創建兩個包裝類。 EF爲我創建表格。我不相信這種方法是有道理的。

  3. 還有一個generic repository pattern其中創建單個存儲庫類來爲所有實體對象提供服務。這對我來說更有意義。但是對別人有意義嗎?鏈接是否是最好的方法?

我很想從其他人那裏獲得關於此主題的一些意見。你是在編寫自己的存儲庫,使用上述之一還是完全不同。請分享。

+1

我想說2號鏈接不是典型的存儲庫模式。通常情況下,您有[DDD](http://en.wikipedia.org/wiki/Domain-driven_design)中每個集合根的存儲庫。這是一個很好的[SO線程](http://stackoverflow.com/questions/1958621/whats-an-aggregate-root)關於這個話題。正如你所提到的,第二個例子似乎只是包裝一張桌子。看起來他們正在實施這種模式,只是爲了實現這種模式而沒有真正的好處。所以我會同意你的看法。 – RobertMS

+0

你可能是對的。但是,在搜索網頁時,我發現大多數示例爲每個實體創建了單獨的包裝,包括我擁有的一些書中的包裝。在這方面,我發佈的鏈接上的代碼看起來很典型。感謝您的鏈接。我會檢查他們。 –

+1

@JonathanWood這是[我最喜歡的解決方案](http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/)(該死的,我用這個鏈接很多)。即,具有通用方法的非通用知識庫接口。它仍然是'DbContext'上的一個相對較薄的包裝,但它允許更簡單的測試。 –

回答

34

我已經使用了#2和#3的混合,但是如果可能的話我更喜歡一個嚴格的通用存儲庫(甚至比在#3的鏈接中建議的更嚴格)。 #1並不好,因爲它在單元測試中表現不佳。

如果你有一個較小的域或者需要收縮你的域允許查詢哪些實體,我想#2或#3定義了實體特定的存儲庫接口,它們自己實現了一個通用的存儲庫 - 這是合理的。然而,我發現爲每個我想查詢的實體編寫一個接口和一個具體的實現是耗盡和不必要的。 public interface IFooRepository : IRepository<Foo>有什麼好處(同樣,除非我需要將開發人員限制爲一組允許的聚合根)?

我只是定義我的通用庫接口,與AddRemoveGetGetDeferredCountFind方法(FIND返回的IQueryable界面,允許LINQ),創建一個具體的通用實現,而收工。我非常依賴Find,因此LINQ。如果我需要多次使用特定的查詢,我使用擴展方法並使用LINQ編寫查詢。

這涵蓋了95%的持續性需求。如果我需要執行某種不能一般完成的持久性操作,則使用本地API ICommand。例如,假設我正在使用NHibernate,並且需要將複雜查詢作爲我的域的一部分,或者我需要執行批量命令。該API看起來大致如下:

// marker interface, mainly used as a generic constraint 
public interface ICommand 
{ 
} 

// commands that return no result, or a non-query 
public interface ICommandNoResult : ICommand 
{ 
    void Execute(); 
} 

// commands that return a result, either a scalar value or record set 
public interface ICommandWithResult<TResult> : ICommand 
{ 
    TResult Execute(); 
} 

// a query command that executes a record set and returns the resulting entities as an enumeration. 
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>> 
{ 
    int Count(); 
} 

// used to create commands at runtime, looking up registered commands in an IoC container or service locator 
public interface ICommandFactory 
{ 
    TCommand Create<TCommand>() where TCommand : ICommand; 
} 

現在我可以創建一個接口來表示一個特定的命令。

public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance> 
{ 
    Decimal MinimumBalance { get; set; } 
} 

我可以創建一個具體的實施和使用原始SQL,NHibernate的HQL,什麼的,和我的服務定位器註冊。

現在我的業務邏輯,我可以做這樣的事情:

var query = factory.Create<IAccountsWithBalanceQuery>(); 
query.MinimumBalance = 100.0; 

var overdueAccounts = query.Execute(); 

你也可以使用一個規範圖案IQuery建立有意義的,用戶輸入驅動的查詢,而不是有600萬的接口令人困惑的屬性,但是假設你沒有發現規範模式本身就是令人困惑的;)。

難題的最後一部分是當您的存儲庫需要執行特定的預處理和-post存儲庫操作時。現在,您可以非常輕鬆地爲特定實體創建通用存儲庫的實現,然後覆蓋相關方法並執行您需要執行的操作,並更新您的IoC或服務定位器註冊並完成相應操作。

但是,有時這種邏輯通過覆蓋存儲庫方法來實現是交叉和尷尬的。所以我創建了IRepositoryBehavior,這基本上是一個事件接收器。 (下面只是我頭頂的一個粗略的定義)

public interface IRepositoryBehavior 
{ 
    void OnAdding(CancellableBehaviorContext context); 
    void OnAdd(BehaviorContext context); 

    void OnGetting(CancellableBehaviorContext context); 
    void OnGet(BehaviorContext context); 

    void OnRemoving(CancellableBehaviorContext context); 
    void OnRemove(BehaviorContext context); 

    void OnFinding(CancellableBehaviorContext context); 
    void OnFind(BehaviorContext context); 

    bool AppliesToEntityType(Type entityType); 
} 

現在,這些行爲可以是任何東西。審計,安全檢查,軟刪除,強制域約束,驗證等。我創建一個行爲,將其註冊到IoC或服務定位器,並修改我的通用存儲庫以接收已註冊的IRepositoryBehavior的集合,並檢查每個行爲針對當前的存儲庫類型,並將操作包裝在每個適用行爲的前/後處理程序中。

下面是一個示例軟刪除行爲(軟刪除意味着當有人要求刪除一個實體時,我們將其標記爲已刪除,因此不能再次返回,但實際上從未實際刪除)。

public SoftDeleteBehavior : IRepositoryBehavior 
{ 
    // omitted 

    public bool AppliesToEntityType(Type entityType) 
    { 
     // check to see if type supports soft deleting 
     return true; 
    } 

    public void OnRemoving(CancellableBehaviorContext context) 
    { 
     var entity = context.Entity as ISoftDeletable; 
     entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated 

     context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity. 
    } 
} 

是的,這基本上是一個NHibernate的事件監聽器的簡化和抽象的實現,但這就是爲什麼我喜歡它。A)我可以單元測試一個行爲,而不會將NHibernate帶入圖片B)我可以在NHibernate之外使用這些行爲(比如說存儲庫是包裝REST服務調用的客戶端實現)C)NH的事件監聽器可以是一個真正的痛苦屁股;)

+0

感謝您的代碼片段。我會花一些時間仔細檢查一下。 –

+0

我終於有更多時間用這個了。我對這些代碼有些驚訝。你似乎在說你喜歡一種非常通用的方法,但你似乎正在創建專門的接口,這比我所研究的例子更具體。爲什麼這需要? (順便說一句,如果你覺得使用源代碼做更完整的寫作,我很想在我的http://www.blackbeltcoder.com網站上發佈這樣的內容。) –

12

我會推薦1號,有一些注意事項。數字2似乎是最常見的,但以我的經驗來看,存儲庫只是結束了查詢的一個凌亂的傾倒場。如果你使用通用的倉庫(2),它只是一個圍繞DBContext的簡單包裝,除非你打算改變ORM的(壞主意),否則有點毫無意義。

但是,當我訪問的DbContext直接我更喜歡使用管道和過濾器模式,因此可以重用共同的邏輯,像

items = DBContext.Clients 
    .ByPhoneNumber('1234%') 
    .ByOrganisation(134); 

的ByPhoneNumber並通過組織只是擴展方法。

+0

謝謝,但是我提出的可能的性能問題呢?如果每次需要時新建一個DBContext,代碼的不同部分就會請求相同的數據,並且不會被緩存。 –

+4

@Johnathan:使用依賴注入,使得任何需要DBContext的東西都會在每個請求生命週期內接收相同的上下文。 –

0

有一個現成的解決方案在URF - Unit of Work & (extensible/generic) Repositories Framework。它會爲你節省很多時間。 他們實現了一個通用的倉庫(也有一個異步倉庫)。對於擴展的存儲庫,他們使用的擴展是這樣的:

 public static decimal GetCustomerOrderTotalByYear(this IRepository<Customer> repository, string customerId, int year) 
    { 
     return repository 
      .Queryable() 
      .Where(c => c.CustomerID == customerId) 
      .SelectMany(c => c.Orders.Where(o => o.OrderDate != null && o.OrderDate.Value.Year == year)) 
      .SelectMany(c => c.OrderDetails) 
      .Select(c => c.Quantity*c.UnitPrice) 
      .Sum(); 
    } 

像QueryObject一些類可能是根據項目的過度勞累,但總體呈它是很好的解決方案,以幫助您啓動和運行。

1

在這裏,我們去在Asp.Net MVC最佳Repository模式:

Repository模式增加了一個應用程序的數據和域層之間的分離層。它還使得應用程序的數據訪問部分可以更好地測試。

數據庫廠(IDatabaseFactory.cs):

public interface IDatabaseFactory : IDisposable 
{ 
    Database_DBEntities Get(); 
} 

數據庫工廠實現(DatabaseFactory.cs):

public class DatabaseFactory : Disposable, IDatabaseFactory 
{ 
    private Database_DBEntities dataContext; 
    public Database_DBEntities Get() 
    { 
     return dataContext ?? (dataContext = new Database_DBEntities()); 
    } 

    protected override void DisposeCore() 
    { 
     if (dataContext != null) 
      dataContext.Dispose(); 
    } 
} 

基本接口(IRepository.cs) :

public interface IRepository<T> where T : class 
{ 
    void Add(T entity); 
    void Update(T entity); 
    void Detach(T entity); 
    void Delete(T entity); 
    T GetById(long Id); 
    T GetById(string Id); 
    T Get(Expression<Func<T, bool>> where); 
    IEnumerable<T> GetAll(); 
    IEnumerable<T> GetMany(Expression<Func<T, bool>> where); 
    void Commit(); 
} 

抽象類(Repository.cs):

public abstract class Repository<T> : IRepository<T> where T : class 
    { 
     private Database_DBEntities dataContext; 
     private readonly IDbSet<T> dbset; 
     protected Repository(IDatabaseFactory databaseFactory) 
     { 
      DatabaseFactory = databaseFactory; 
      dbset = DataContext.Set<T>(); 

     } 

     /// <summary> 
     /// Property for the databasefactory instance 
     /// </summary> 
     protected IDatabaseFactory DatabaseFactory 
     { 
      get; 
      private set; 
     } 

     /// <summary> 
     /// Property for the datacontext instance 
     /// </summary> 
     protected Database_DBEntities DataContext 
     { 
      get { return dataContext ?? (dataContext = DatabaseFactory.Get()); } 
     } 

     /// <summary> 
     /// For adding entity 
     /// </summary> 
     /// <param name="entity"></param> 
     public virtual void Add(T entity) 
     { 

      try 
      { 
       dbset.Add(entity); 
       // dbset.Attach(entity); 
       dataContext.Entry(entity).State = EntityState.Added; 
       int iresult = dataContext.SaveChanges(); 
      } 
      catch (UpdateException ex) 
      { 

      } 
      catch (DbUpdateException ex) //DbContext 
      { 

      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 

     } 

     /// <summary> 
     /// For updating entity 
     /// </summary> 
     /// <param name="entity"></param> 
     public virtual void Update(T entity) 
     { 
      try 
      { 
       // dbset.Attach(entity); 
       dbset.Add(entity); 
       dataContext.Entry(entity).State = EntityState.Modified; 
       int iresult = dataContext.SaveChanges(); 
      } 
      catch (UpdateException ex) 
      { 
       throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex); 
      } 
      catch (DbUpdateException ex) //DbContext 
      { 
       throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex); 
      } 
      catch (Exception ex) { 
       throw ex; 
      } 
     } 



     /// <summary> 
     /// for deleting entity with class 
     /// </summary> 
     /// <param name="entity"></param> 
     public virtual void Delete(T entity) 
     { 
      dbset.Remove(entity); 
      int iresult = dataContext.SaveChanges(); 
     } 


     //To commit save changes 
     public void Commit() 
     { 
      //still needs modification accordingly 
      DataContext.SaveChanges(); 
     } 

     /// <summary> 
     /// Fetches values as per the int64 id value 
     /// </summary> 
     /// <param name="id"></param> 
     /// <returns></returns> 
     public virtual T GetById(long id) 
     { 
      return dbset.Find(id); 
     } 

     /// <summary> 
     /// Fetches values as per the string id input 
     /// </summary> 
     /// <param name="id"></param> 
     /// <returns></returns> 
     public virtual T GetById(string id) 
     { 
      return dbset.Find(id); 
     } 

     /// <summary> 
     /// fetches all the records 
     /// </summary> 
     /// <returns></returns> 
     public virtual IEnumerable<T> GetAll() 
     { 
      return dbset.AsNoTracking().ToList(); 
     } 

     /// <summary> 
     /// Fetches records as per the predicate condition 
     /// </summary> 
     /// <param name="where"></param> 
     /// <returns></returns> 
     public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where) 
     { 
      return dbset.Where(where).ToList(); 
     } 

     /// <summary> 
     /// 
     /// </summary> 
     /// <param name="entity"></param> 
     public void Detach(T entity) 
     { 
      dataContext.Entry(entity).State = EntityState.Detached; 
     } 

     /// <summary> 
     /// fetches single records as per the predicate condition 
     /// </summary> 
     /// <param name="where"></param> 
     /// <returns></returns> 
     public T Get(Expression<Func<T, bool>> where) 
     { 
      return dbset.Where(where).FirstOrDefault<T>(); 
     } 

    } 

現在主要的問題是如何訪問控制器該存儲庫模式在這裏,我們去:

1.有用戶型號:

public partial class User 
{ 
     public int Id { get; set; } 
     public string Name { get; set; } 
} 

2.現在你必須創建你的usermodel的倉儲類

public class UserRepository : Repository<User>, IUserRepository 
{ 
    private Database_DBEntities dataContext; 

    protected IDatabaseFactory DatabaseFactory 
    { 
     get; 
     private set; 
    } 

    public UserRepository(IDatabaseFactory databaseFactory) 
     : base(databaseFactory) 
    { 
     DatabaseFactory = databaseFactory; 
    } 

    protected Database_DBEntities DataContext 
    { 
     get { return dataContext ?? (dataContext = DatabaseFactory.Get()); } 
    } 

    public interface IUserRepository : IRepository<User> 
    { 
    } 
} 

3.現在你必須創建UserService接口(IUserService.cs)所有的CRUD方法:

public interface IUserService 
{ 

    #region User Details 
    List<User> GetAllUsers(); 
    int SaveUserDetails(User Usermodel); 
    int UpdateUserDetails(User Usermodel); 
    int DeleteUserDetails(int Id); 
    #endregion 

} 

4.現在您必須創建包含所有CRUD方法的UserService接口(UserService.cs):

public class UserService : IUserService 
{ 
    IUserRepository _userRepository; 
    public UserService() { } 
    public UserService(IUserRepository userRepository) 
    { 
    this._userRepository = userRepository; 
    } 
    public List<User> GetAllUsers() 
    { 
     try 
     { 
      IEnumerable<User> liUser = _userRepository.GetAll(); 
      return liUser.ToList(); 
     } 
     catch (Exception ex) 
     { 
      throw ex; 
     } 
    } 
    /// <summary> 
    /// Saves the User details. 
    /// </summary> 
    /// <param name="User">The deptmodel.</param> 
    /// <returns></returns> 
    public int SaveUserDetails(User Usermodel) 
    { 
     try 
     { 
      if (Usermodel != null) 
      { 
       _userRepository.Add(Usermodel); 
       return 1; 
      } 
      else 
       return 0; 
     } 
     catch 
     { 
      throw; 
     } 

    } 

    /// <summary> 
    /// Updates the User details. 
    /// </summary> 
    /// <param name="User">The deptmodel.</param> 
    /// <returns></returns> 
    public int UpdateUserDetails(User Usermodel) 
    { 
     try 
     { 
      if (Usermodel != null) 
      { 
       _userRepository.Update(Usermodel); 
       return 1; 
      } 
      else 
       return 0; 
     } 
     catch 
     { 
      throw; 
     } 
    } 

    /// <summary> 
    /// Deletes the User details. 
    /// </summary> 
    /// <param name="Id">The code identifier.</param> 
    /// <returns></returns> 
    public int DeleteUserDetails(int Id) 
    { 
     try 
     { 
      User Usermodel = _userRepository.GetById(Id); 
      if (Usermodel != null) 
      { 
       _userRepository.Delete(Usermodel); 
       return 1; 
      } 
      else 
       return 0; 
     } 
     catch 
     { 
      throw; 
     } 
    } 

} 

5。現在大家的Repository模式設定時,你可以在用戶控制器訪問所有數據:

//Here is the User Controller 
public class UserProfileController : Controller 
{ 

    IUserService _userservice; 
    public CustomerProfileController(IUserService userservice) 
    { 
     this._userservice = userservice; 
    } 

    [HttpPost] 
    public ActionResult GetAllUsers(int id) 
    { 
    User objUser=new User(); 

    objUser = _userservice.GetAllUsers().Where(x => x.Id == id).FirstOrDefault(); 

    } 
} 

乾杯!

+0

似乎很多代碼是已經使用DbContext爲您實施。不知道這種方法如何有意義。 –