倉儲模式Repository的選擇與設計

首次接觸倉儲的概念來自Eric Evans 的經典著作《領域驅動設計-軟件核心複雜性應對之道》,但書中沒有具體實現。如何實現倉儲模式,在我這幾年的使用過程中也積累了一些具體的實施經驗。根據項目的大小、可維護性、可擴展性,以及併發我們可以做以下幾種設計;

 

1、項目小,擴展性差

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)。但如果項目小且項目生存週期短可選擇此模式進行快速搭建。


 

2、項目大,可擴展性好,不對併發做處理。

因爲項目要求高擴展性,每次修改都對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();
            }
        }
    }

 

這裏不對代碼的實現合理性做討論,只是爲了說明倉儲模式的設計。

而在業務層中的使用如下:

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();
        }

 

此倉儲模式在實際使用中稍顯複雜,每添加一個實體,需要添加對應的接口和實現兩個文件,但是這裏的一點複雜度換來代碼的高擴展性和維護性是值得的。

 

3、項目龐大,擴展性高,有併發處理需求

因爲項目涉及高併發,採用倉儲模式+工作單元模式的設計,使用工作單元的原因是可以提高數據庫寫操作負載,並且在倉儲模式中可以根據不同的數據庫鏈接字符串讀不同的庫。

對於併發的,可以分爲多線程、並行處理、異步編程、響應式編程。(引用:《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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章