仓储模式Repository的选择与设计
Posted gonm
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仓储模式Repository的选择与设计相关的知识,希望对你有一定的参考价值。
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。
引自:https://www.cnblogs.com/Zhang-Xiang/p/7839540.html
本文版权归作者和博客园共有,欢迎转载。未经作者同意下,必须在文章页面明显标出原文链接及作者,否则保留追究法律责任的权利。
如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
以上是关于仓储模式Repository的选择与设计的主要内容,如果未能解决你的问题,请参考以下文章
.NET Core MongoDB数据仓储和工作单元模式封装