首次接觸倉儲的概念來自Eric Evans 的經典著做《領域驅動設計-軟件核心複雜性應對之道》,但書中沒有具體實現。如何實現倉儲模式,在我這幾年的使用過程當中也積累了一些具體的實施經驗。根據項目的大小、可維護性、可擴展性,以及併發咱們能夠作如下幾種設計;sql
一、項目小,擴展性差數據庫
public interface IRepository<T> where T : class,new() { /// <summary> /// 建立對象 /// </summary> /// <param name="model"></param> /// <returns></returns> T Create(T model); /// <summary> /// 更新對象 /// </summary> /// <param name="model"></param> /// <returns></returns> T Update(T model); /// <summary> /// 根據對象全局惟一標識檢索對象 /// </summary> /// <param name="guid"></param> /// <returns></returns> T Retrieve(Guid guid); /// <summary> /// 根據條件表達式檢索對象 /// </summary> /// <param name="expression">條件表達式</param> /// <returns></returns> /// <exception cref = "ArgumentNullException" > source 爲 null</exception> T Retrieve(Expression<Func<T, bool>> expression); /// <summary> /// 根據對象全局惟一標識刪除對象 /// </summary> /// <param name="guid">對象全局惟一標識</param> /// <returns>刪除的對象數量</returns> int Delete(Guid guid); /// <summary> /// 根據對象全局惟一標識集合刪除對象集合 /// </summary> /// <param name="guids">全局惟一標識集合</param> /// <returns>刪除的對象數量</returns> int BatchDelete(IList<Guid> guids); List<T> GetAll(); List<T> GetAll(Expression<Func<T, bool>> expression, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take, out int total); }
IRepository接口包含了CRUD操做,若是在業務中還須要擴展,只需在IRepository接口中添加便可。
public class RepositoryImpl<T> : IRepository<T> where T : class, new() { protected readonly string ConnectionString; protected RepositoryImpl(ISqlHelp sqlHelp) { ConnectionString = sqlHelp.SQLConnectionString(); } public int BatchDelete(IList<Guid> guids) { using (var dbcontext = new DbContext(ConnectionString)) { foreach (var item in guids) { var model = dbcontext.Set<T>().Find(item); dbcontext.Entry(model).State = EntityState.Deleted; } return dbcontext.SaveChanges(); } } public T Create(T model) { using (var dbcontext = new DbContext(ConnectionString)) { dbcontext.Entry(model).State = EntityState.Added; var createRowCount = dbcontext.SaveChanges(); return createRowCount > 0 ? model : null; } } /// <summary> /// 刪除模型 /// </summary> /// <param name="guid">指定的全局標識</param> /// <returns>刪除數量</returns> /// <exception cref="ArgumentOutOfRangeException"></exception> public int Delete(Guid guid) { using (var dbcontext = new DbContext(ConnectionString)) { var model = dbcontext.Set<T>().Find(guid); if (model == null) throw new ArgumentOutOfRangeException(nameof(guid)); dbcontext.Entry(model).State = EntityState.Deleted; return dbcontext.SaveChanges(); } } public List<T> GetAll() { using (var dbcontext = new DbContext(ConnectionString)) { return dbcontext.Set<T>().Where(q => true).ToList(); } } public List<T> GetAll(Expression<Func<T, bool>> expression, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take, out int total) { using (var dbcontext = new DbContext(ConnectionString)) { total = dbcontext.Set<T>().Where(expression).Count(); switch (sortOrder) { case SortOrder.Ascending: return dbcontext.Set<T>().Where(expression).OrderBy(sortPredicate).Skip(skip).Take(take).ToList(); case SortOrder.Descending: return dbcontext.Set<T>().Where(expression).OrderByDescending(sortPredicate).Skip(skip).Take(take).ToList(); } throw new InvalidOperationException("基於分頁功能的查詢必須指定排序字段和排序順序。"); } } /// <summary> /// 返回序列中的第一個元素 /// </summary> /// <param name="expression">查詢表達式</param> /// <returns>T</returns> /// <exception cref="ArgumentNullException">source 爲 null</exception> public T Retrieve(Expression<Func<T, bool>> expression) { using (var dbcontext = new DbContext(ConnectionString)) { return dbcontext.Set<T>().FirstOrDefault(expression); } } public T Retrieve(Guid guid) { using (var dbcontext = new DbContext(ConnectionString)) { return dbcontext.Set<T>().Find(guid); } } public T Update(T model) { using (var dbcontext = new DbContext(ConnectionString)) { dbcontext.Entry(model).State = EntityState.Modified; var updateRowAcount = dbcontext.SaveChanges(); return updateRowAcount > 0 ? model : null; } } }
RepositoryImpl爲IRepository接口的實現。其中ISqlHelp接口包含獲取數據庫連接字符串的功能,DbContext爲EntityFramework類庫。
public sealed class UserServer { private readonly IRepository<User> _userRepository; public UserServer(IRepository<User> userRepository) { _userRepository = userRepository; } public void CreateUser() { var user = new User(); _userRepository.Create(user); } }
這是最簡單的倉儲使用方式,優勢是簡單、快速,缺點是擴展性差且違反開放-關閉原則(Open-Close Principle)。但若是項目小且項目生存週期短可選擇此模式進行快速搭建。express
二、項目大,可擴展性好,不對併發作處理。編程
由於項目要求高擴展性,每次修改都對IRepository修改也違反軟件設計原則。這裏IRepository接口不變,可是RepositoryImpl作以下修改:多線程
public class RepositoryImpl<T> : IRepository<T> where T : class, new() { protected readonly string ConnectionString; protected RepositoryImpl(ISqlHelp sqlHelp) { ConnectionString = sqlHelp.SQLConnectionString(); } public virtual int BatchDelete(IList<Guid> guids) { using (var dbcontext = new DbContext(ConnectionString)) { foreach (var item in guids) { var model = dbcontext.Set<T>().Find(item); dbcontext.Entry(model).State = EntityState.Deleted; } return dbcontext.SaveChanges(); } } public virtual T Create(T model) { using (var dbcontext = new DbContext(ConnectionString)) { dbcontext.Entry(model).State = EntityState.Added; var createRowCount = dbcontext.SaveChanges(); return createRowCount > 0 ? model : null; } } /// <summary> /// 刪除模型 /// </summary> /// <param name="guid">指定的全局標識</param> /// <returns>刪除數量</returns> /// <exception cref="ArgumentOutOfRangeException"></exception> public virtual int Delete(Guid guid) { using (var dbcontext = new DbContext(ConnectionString)) { var model = dbcontext.Set<T>().Find(guid); if (model == null) throw new ArgumentOutOfRangeException(nameof(guid)); dbcontext.Entry(model).State = EntityState.Deleted; return dbcontext.SaveChanges(); } } public virtual List<T> GetAll() { using (var dbcontext = new DbContext(ConnectionString)) { return dbcontext.Set<T>().Where(q => true).ToList(); } } public virtual List<T> GetAll(Expression<Func<T, bool>> expression, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take, out int total) { using (var dbcontext = new DbContext(ConnectionString)) { total = dbcontext.Set<T>().Where(expression).Count(); switch (sortOrder) { case SortOrder.Ascending: return dbcontext.Set<T>().Where(expression).OrderBy(sortPredicate).Skip(skip).Take(take).ToList(); case SortOrder.Descending: return dbcontext.Set<T>().Where(expression).OrderByDescending(sortPredicate).Skip(skip).Take(take).ToList(); } throw new InvalidOperationException("基於分頁功能的查詢必須指定排序字段和排序順序。"); } } /// <summary> /// 返回序列中的第一個元素 /// </summary> /// <param name="expression">查詢表達式</param> /// <returns>T</returns> /// <exception cref="ArgumentNullException">source 爲 null</exception> public virtual T Retrieve(Expression<Func<T, bool>> expression) { using (var dbcontext = new DbContext(ConnectionString)) { return dbcontext.Set<T>().FirstOrDefault(expression); } } public virtual T Retrieve(Guid guid) { using (var dbcontext = new DbContext(ConnectionString)) { return dbcontext.Set<T>().Find(guid); } } public virtual T Update(T model) { using (var dbcontext = new DbContext(ConnectionString)) { dbcontext.Entry(model).State = EntityState.Modified; var updateRowAcount = dbcontext.SaveChanges(); return updateRowAcount > 0 ? model : null; } } } }
即在每一個方法實現上加上了virtual關鍵字使方法能夠重載。在示例1中業務使用User對象的倉儲方式爲IRepository<User>,若是業務須要針對User對象集合作批量修改,這時就必須去修改IRepository和RepositoryImpl,因此這裏將添加接口IUserRepository,併發
/// <summary> /// 用戶倉儲接口 /// </summary> public interface IUserRepository:IRepository<User> { /// <summary> /// 批量修改用戶生日 /// </summary> void BatchUpdateUserBirthday(); }
UserRepositoryImpl實現爲異步
public sealed class UserRepositoryImpl: RepositoryImpl<User>,IUserRepository { public UserRepositoryImpl(ISqlHelp sqlHelp) : base(sqlHelp) { } public void BatchUpdateUserBirthday() { using (var dbcontext = new DbContext(ConnectionString)) { var usersFromDb = dbcontext.Set<User>().Where(q => q.Name.Equals("zhang")); foreach (var item in usersFromDb) { item.Name = "wang"; dbcontext.Entry(item).State = EntityState.Modified; } dbcontext.SaveChanges(); } } }
這裏不對代碼的實現合理性作討論,只是爲了說明倉儲模式的設計。async
而在業務層中的使用以下:異步編程
public sealed class UserServer { private readonly IUserRepository _userRepository; public UserServer(IUserRepository userRepository) { _userRepository = userRepository; } public void CreateUser() { var user = new User(); _userRepository.Create(user); } public void BatchUpdateBirthDay() { _userRepository.BatchUpdateUserBirthday(); }
此倉儲模式在實際使用中稍顯複雜,每添加一個實體,須要添加對應的接口和實現兩個文件,可是這裏的一點複雜度換來代碼的高擴展性和維護性是值得的。高併發
三、項目龐大,擴展性高,有併發處理需求
由於項目涉及高併發,採用倉儲模式+工做單元模式的設計,使用工做單元的緣由是能夠提升數據庫寫操做負載,而且在倉儲模式中能夠根據不一樣的數據庫連接字符串讀不一樣的庫。
對於併發的,能夠分爲多線程、並行處理、異步編程、響應式編程。(引用:《Concurrency in C# Cookbook》—Author,Stephen Cleary)
在倉儲中我會使用異步編程實現併發。
倉儲接口以下:
public interface IRepository<T> where T:class,IEntity,new () { /// <summary> /// 根據條件表達式獲取集合 /// </summary> /// <param name="predicate"></param> /// <returns></returns> Task<List<T>> FindByAsync(Expression<Func<T, bool>> predicate); IQueryable<T> FindQueryableByAsync(Expression<Func<T, bool>> predicate); /// <summary> /// 根據對象全局惟一標識檢索對象 /// </summary> /// <param name="ID"></param> /// <returns></returns> Task<T> RetrieveAsync(Guid ID); /// <summary> /// 根據條件表達式檢索對象 /// </summary> /// <param name="predicate"></param> /// <returns></returns> Task<T> RetrieveAsync(Expression<Func<T, bool>> predicate); /// <summary> /// 獲取全部數據 /// </summary> /// <returns></returns> Task<List<T>> GetAllAsync(); /// <summary> /// 獲取全部數據 /// </summary> /// <returns></returns> List<T> GetAll(); /// <summary> /// 根據條件表示分頁獲取數據集合 /// </summary> /// <param name="predicate">斷言表達式</param> /// <param name="sortPredicate">排序斷言</param> /// <param name="sortOrder">排序方式</param> /// <param name="skip">跳過序列中指定數量的元素,而後返回剩餘的元素</param> /// <param name="take">從序列的開頭返回指定數量的連續元素</param> /// <returns>item1:數據集合;item2:數據總數</returns> Task<Tuple<List<T>,int>> GetAllAsync(Expression<Func<T, bool>> predicate, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take); }
工做單元接口以下:
/// <summary> /// Unit Of Work Pattern /// </summary> public interface IUnitOfWork : IDisposable { DbContext DbContext { get; set; } /// <summary> /// 提交全部更改 /// </summary> Task CommitAsync(); #region Methods /// <summary> /// 將指定的聚合根標註爲「新建」狀態。 /// </summary> /// <typeparam name="T">須要標註狀態的聚合根類型。</typeparam> /// <param name="obj">須要標註狀態的聚合根。</param> void RegisterNew<T>(T obj) where T : class, IEntity; /// <summary> /// 將指定的聚合根標註爲「更改」狀態。 /// </summary> /// <typeparam name="T">須要標註狀態的聚合根類型。</typeparam> /// <param name="obj">須要標註狀態的聚合根。</param> void RegisterModified<T>(T obj) where T : class; /// <summary> /// 將指定的聚合根標註爲「刪除」狀態。 /// </summary> /// <typeparam name="T">須要標註狀態的聚合根類型。</typeparam> /// <param name="obj">須要標註狀態的聚合根。</param> void RegisterDeleted<T>(T obj) where T : class; #endregion }
倉儲實現以下:
public class RepositoryImpl<T> : IRepository<T> where T : class, IEntity, new() { protected readonly DbContext Context; protected RepositoryImpl(IContextHelper contextHelper) { Context = contextHelper.DbContext; } public virtual async Task<List<T>> FindByAsync(Expression<Func<T, bool>> predicate) { return await Context.Set<T>().Where(predicate).ToListAsync(); } public virtual IQueryable<T> FindQueryableByAsync(Expression<Func<T, bool>> predicate) { return Context.Set<T>().Where(predicate); } public virtual async Task<List<T>> GetAllAsync() { return await Context.Set<T>().ToListAsync(); } public List<T> GetAll() { return Context.Set<T>().ToList(); } public virtual async Task<Tuple<List<T>, int>> GetAllAsync(Expression<Func<T, bool>> predicate, Expression<Func<T, dynamic>> sortPredicate, SortOrder sortOrder, int skip, int take) { var result = Context.Set<T>().Where(predicate); var total = result.Count(); switch (sortOrder) { case SortOrder.Ascending: var resultAscPaged = await Context.Set<T>().Where(predicate).OrderBy(sortPredicate).Skip(skip).Take(take).ToListAsync(); return new Tuple<List<T>, int>(resultAscPaged, total); case SortOrder.Descending: var resultDescPaged = await Context.Set<T>().Where(predicate) .OrderByDescending(sortPredicate) .Skip(skip) .Take(take).ToListAsync(); return new Tuple<List<T>, int>(resultDescPaged, total); } throw new InvalidOperationException("基於分頁功能的查詢必須指定排序字段和排序順序。"); } public virtual async Task<T> RetrieveAsync(Expression<Func<T, bool>> predicate) { return await Context.Set<T>().FirstOrDefaultAsync(predicate); } public virtual async Task<T> RetrieveAsync(Guid id) { return await Context.Set<T>().FindAsync(id); } }
工做單元實現以下:
public class UnitOfWork : IUnitOfWork { public DbContext DbContext { get; set; } public UnitOfWork(IContextHelper contextHelp) { DbContext = contextHelp.DbContext; } /// <summary> /// Saves all pending changes /// </summary> /// <returns>The number of objects in an Added, Modified, or Deleted state</returns> public virtual async Task CommitAsync() { // Save changes with the default options try { await DbContext.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { ex.Entries.Single().Reload(); } } /// <summary> /// Disposes the current object /// </summary> public virtual void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Disposes all external resources. /// </summary> /// <param name="disposing">The dispose indicator.</param> private void Dispose(bool disposing) { if (!disposing) return; if (DbContext == null) return; DbContext.Dispose(); DbContext = null; } public virtual void RegisterNew<TEntity>(TEntity obj) where TEntity : class, IEntity { DbContext.Set<TEntity>().Add(obj); } public virtual void RegisterModified<TEntity>(TEntity obj) where TEntity : class { DbContext.Entry(obj).State = EntityState.Modified; } public virtual void RegisterDeleted<TEntity>(TEntity obj) where TEntity : class { DbContext.Entry(obj).State = EntityState.Deleted; } }
在業務層中的使用同2。