首次接触仓储的概念来自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 > ? 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 > ? 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 > ? 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 > ? 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。

仓储模式Repository的选择与设计的更多相关文章

  1. 单元操作和仓储模式 repository+unitOfWork

    仓储和工作单元模式是用来在数据访问层和业务逻辑层之间创建一个抽象层.应用这些模式,可以帮助用来隔离你的程序在数据存储变化. 在数据源层和业务层之间增加一个repository层进行协调,有如下作用:1 ...

  2. .Net Core之仓储(Repository)模式

    我们经常在项目中使用仓储(Repository)模式,来实现解耦数据访问层与业务层.那在.net core使用EF core又是怎么做的呢? 现在我分享一下我的实现方案: 一.在领域层创建Reposi ...

  3. 4.在MVC中使用仓储模式进行增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-using-the-repository-pattern-in-mvc/ 系列目录: ...

  4. 5.在MVC中使用泛型仓储模式和工作单元来进行增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  5. 【摘录】使用实体框架、Dapper和Chain的仓储模式实现策略

    以下文章摘录来自InfoQ,是一篇不错的软问,大家细细的品味 关键要点: Dapper这类微ORM(Micro-ORM)虽然提供了最好的性能,但也需要去做最多的工作. 在无需复杂对象图时,Chain这 ...

  6. 在MVC中使用泛型仓储模式和工作单元来进行增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  7. 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  8. MVC5+EF6 入门完整教程十一:细说MVC中仓储模式的应用

    摘要: 第一阶段1~10篇已经覆盖了MVC开发必要的基本知识. 第二阶段11-20篇将会侧重于专题的讲解,一篇文章解决一个实际问题. 根据园友的反馈, 本篇文章将会先对呼声最高的仓储模式进行讲解. 文 ...

  9. Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)

    很久没有写博客了,一些读者也经常问问一些问题,不过最近我确实也很忙,除了处理日常工作外,平常主要的时间也花在了继续研究微软的实体框架(EntityFramework)方面了.这个实体框架加入了很多特性 ...

随机推荐

  1. 为什么hexo预览功能总是间歇性失效?

    个人主题:yilia 有的文章可以正常截断,有的文章不行. 开始我以为这是hexo的一个小bug,但是后来通过查阅GitHub和知乎等网站发现这完全是由于我自己的粗心造成的…手动滑稽 hexo pag ...

  2. C#实现 单点登录(SSO)

    SSO的基本概念 SSO英文全称Single Sign On(单点登录).SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.它包括可以将这次主要的登录映射到其他应用中用于同 ...

  3. PBRT笔记(7)——反射模型

    基础术语 表面反射可以分为4大类: diffuse 漫反射 glossy specular 镜面反射高光 perfect specular 完美反射高光 retro-reflective distri ...

  4. Spark累加器(Accumulator)陷阱及解决办法

    累加器(accumulator)是Spark中提供的一种分布式的变量机制,其原理类似于mapreduce,即分布式的改变,然后聚合这些改变.累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数 ...

  5. 20175324 2018-2019-2 《Java程序设计》第5周学习总结

    20175324 2018-2019-2 <Java程序设计>第5周学习总结 教材学习内容总结 抽象类和具体类的区别在于抽象类中有抽象方法而具体类中没有.且抽象类不能实例化. 接口:如果一 ...

  6. SpringAop注解实现日志的存储

    一.介绍 1.AOP的作用 在OOP中,正是这种分散在各处且与对象核心功能无关的代码(横切代码)的存在,使得模块复用难度增加.AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封 ...

  7. 2003server r2 + sql 2000 sp4 环境配置

    由于工作需求需要配置一个windows 2003 server r2 + sql 2000 sp4的环境: 一.2003server准备系统: msdn 下载 分清x86还是x64 一共有两个cd准备 ...

  8. python 可迭代对象 迭代器 生成器总结

    可迭代对象 只要有魔法方法__iter__的就是可迭代对象  list和tuple和dict都是可迭代对象 迭代器 只要有魔法方法__iter__和__next__的就是可迭代对象 生成器 只要含有y ...

  9. vue单页面应用刷新网页后vuex的state数据丢失的解决方案

    1. 产生原因其实很简单,因为store里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,store里面的数据就会被重新赋值. 2. 解决思路一种是state里的数据全部是通过请求 ...

  10. ssh 报error: kex protocol error: type 30 seq 1

    由于近期服务器升级了openssl,在使用navicat连接数据库报 查看日志 sshd[1990]: error: kex protocol error: type 30 seq 1 [preaut ...