仓储(Repository)和工作单元模式(UnitOfWork)
仓储和工作单元模式
仓储模式
为什么要用仓储模式
通常不建议在业务逻辑层直接访问数据库。因为这样可能会导致如下结果:
- 重复的代码
- 编程错误的可能性更高
- 业务数据的弱类型
- 更难集中处理数据,比如缓存
- 无法轻松地从外部依赖项测试业务逻辑
在业务逻辑层通过仓库模式访问数据则可以实现如下特点:
- 最大化可以用自动化测试的代码量,并隔离数据层以支持单元测试。
- 对数据集中管理、提供一致的访问规则和逻辑。
- 通过将业务逻辑与数据或服务访问逻辑分隔开,从而提高代码的可维护性和可读性。
- 使用强类型的
Entity以便在编译时识别问题而不是在运行时
实现仓储模式
使用仓储模式是为了分离业务层和数据源层,并实现业务层的Model和数据源层的Model映射。(ViewModel和Entity之间的映射)。即业务逻辑层应该和数据源层无关,业务层只关心结果,数据源层关心细节。
数据源层和业务层之间的分离有三个好处:
- 集中了数据逻辑或Web服务访问逻辑。
- 为单元测试提供了一个替代点。
- 提供了一种灵活的体系结构,可以作为应用程序的整体设计进行调整。
一、定义仓储接口
所有的仓储要实现该接口。该接口定义了对数据的基本操作。
public interface IRepository<TEntity> where TEntity : class
{
#region 属性
//IQueryable Entities { get; }
#endregion
#region 公共方法
void Insert(TEntity entity);
void Insert(IEnumerable<TEntity> entities);
void Delete(object id);
void Delete(TEntity entity);
void Delete(IEnumerable<TEntity> entities);
void Update(TEntity entity);
TEntity GetByKey(object key);
#endregion
}
二、实现泛型仓储基类
该类为仓储的泛型基类,实现之前定义的仓储接口(IRepository),并包含数据上下文(DbContext),数据集(DataSet)。
每个表都会对应一个实体(Entity)。每个实体(Entity)对应一个仓储。把实体作为泛型仓储基类的参数,来实现每个实体对应的仓储。
(使用泛型仓储基类可以把实体作为泛型参数来创建对应的仓储。)
//泛型仓储基类
public class EFBaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
//数据上下文
internal DbContext context;
//数据集
internal DbSet<TEntity> dbSet;
public EFBaseRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
//public IQueryable Entities => context.Set<TEntity>();
public void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public void Delete(IEnumerable<TEntity> entities)
{
dbSet.RemoveRange(entities);
}
public void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public TEntity GetByKey(object key)
{
return dbSet.Find(key);
}
public void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public void Insert(IEnumerable<TEntity> entities)
{
dbSet.AddRange(entities);
}
public void Update(TEntity entity)
{
dbSet.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "", int topNum = 0)
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
query = orderBy(query);
}
if (topNum != 0)
{
return query.Take(topNum);
}
else
{
return query.ToList();
}
}
}
三、访问数据
可以把对Person的相关操作封装到一个类中。在该类中实现PersonRepository(Person仓储),操作PersonRepository来操作数据。
(数据库有一个Person表,代码中有一个TPerson实体)
(该类提供与业务逻辑无关的仓储操作)
public class PersonService
{
private EFBaseRepository<TPerson> _personRepository;
public PersonService(DbContext dbContext)
{
var context = dbContext;
//实现Person仓储,TPerson为对应的Entity
_personRepository = new EFBaseRepository<TPerson>(context);
}
public IEnumerable<TPerson> Get()
{
return _personRepository.Get();
}
public bool AddPerson(TPerson p)
{
try
{
_personRepository.Insert(p);
}
catch (Exception ex)
{
return false;
}
return true;
}
public bool EditPerson(TPerson p)
{
try
{
_personRepository.Update(p);
}
catch (Exception ex)
{
return false;
}
return true;
}
public bool DeletePerson(TPerson p)
{
try
{
_personRepository.Delete(p);
}
catch (Exception)
{
return false;
}
return true;
}
}
四、ViewModel和Entity的映射
该类是对PersonService的封装,是为了提供同一数据上下文,和对数据上下文的释放,及ViewModle和Entity的映射。
该类中每个方法对应一个数据上下文。如果有需要对多个表操作,将这些操作封装到一个数据上下文中。数据上下文的释放在每个方法中实现。
(所有与业务逻辑相关的操作在该类实现)
public class PersonManage
{
public IList<PersonVM> GetPersons()
{
using (var context = new RepositoryDemoEntities())
{
var list = new PersonService(context).Get();
var result = new List<PersonVM>();
foreach (var item in list)
{
result.Add(new PersonVM { Name = item.Name, Age = item.Age, Home = item.Home, PersonID = item.Id });
}
return result;
}
}
public bool AddPerson(PersonVM p)
{
using (var context = new RepositoryDemoEntities())
{
var result = new PersonService(context).AddPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
context.SaveChanges();
return result;
}
}
public bool DeletePerson(PersonVM p)
{
using (var context = new RepositoryDemoEntities())
{
var result = new PersonService(context).DeletePerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
context.SaveChanges();
return result;
}
}
public bool EditPerson(PersonVM p)
{
using (var context = new RepositoryDemoEntities())
{
var result = new PersonService(context).EditPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
context.SaveChanges();
return result;
}
}
}
五、在Test中测试
仓储模式使得更容易实现单元测试
- 添加项目引用
- 设置数据库连接字符串
- 添加
EntityFramework包即可对每个方法测试
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestShowPerson()
{
var res = new PersonManage().GetPersons();
Assert.AreNotEqual(0, res.Count);
}
[TestMethod]
public void TestAddPerson()
{
var p = new PersonVM { Home = "zhengzhou", Age = 22, Name = "Jessica", PersonID = 3 };
var res = new PersonManage().AddPerson(p);
Assert.IsTrue(res);
}
[TestMethod]
public void TestEditPerson()
{
var persons = new PersonManage().GetPersons();
var p = persons[0];
p.Name = "fixed";
var res = new PersonManage().EditPerson(p);
Assert.IsTrue(res);
}
[TestMethod]
public void TestDeletePerson()
{
var persons = new PersonManage().GetPersons();
var p = persons[0];
var res = new PersonManage().DeletePerson(p);
Assert.IsTrue(res);
}
}
小结:
仓储模式通过对数据库操作的封装使数据访问有一致性和对应用层和数据层的隔离,降低代码的耦合性,更加容易实现单元测试。
工作单元模式
工作单元模式是“维护一个被业务事务影响的对象列表,协调变化的写入和并发问题的解决”
比如:新入校一个同学,需要在班级,学校,学生,课程等多个表里同时操作。这些表要么都完成,要么都不完成。具有一致性。
在仓储模式中使用工作单元模式是为了当你操作多个仓储时,共用一个数据上下文(DbContext)使得这些仓储具有一致性。
在Entity Framework中可以把DbContext当作是一个工作单元。在同一个DbContext对多个仓储操作。所以工作单元模式并不是一定要自己实现,通过Entity Framework也可以实现。
上面的仓储模式其实通过对DbContext的使用了也实现了工作单元模式。
还是简单说下如何实现自定义的工作单元 (如果要对每个操作都产生记录的话,可以扩展自定义工作单元来实现)
自定义工作单元
一、定义IUnitOfWork接口
/// <summary>
/// 工作单元接口
/// </summary>
public interface IUnitOfWork
{
/// <summary>
/// 保存当前单元操作的结果
/// </summary>
/// <returns></returns>
void Save();
}
二、定义UnitOfWork类
UnitOfWork包含了所有的仓储,及一个数据上下文,该类实现IDisposable接口(该接口的方法中释放数据上下文)。
public class UnitOfWork : IUnitOfWork, IDisposable
{
private RepositoryDemoEntities1 context = new RepositoryDemoEntities1();
private EFBaseRepository<TPerson> _personRepository;
public EFBaseRepository<TPerson> PersonRepository
{
get
{
return _personRepository ?? new EFBaseRepository<TPerson>(context);
}
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
三、实现UnitOfWork实例。通过该实例访问仓储。
定义一个UnitOfWork的字段,通过构造函数实例化该UnitOfWork
(该类提供与业务逻辑无关的仓储操作)
public class PersonService
{
private UnitOfWork unit;
public PersonService(UnitOfWork unitOfWork)
{
unit = unitOfWork;
}
public IEnumerable<TPerson> Get()
{
return unit.PersonRepository.Get();
}
public bool AddPerson(TPerson p)
{
try
{
unit.PersonRepository.Insert(p);
}
catch (Exception ex)
{
return false;
}
return true;
}
public bool EditPerson(TPerson p)
{
try
{
unit.PersonRepository.Update(p);
}
catch (Exception ex)
{
return false;
}
return true;
}
public bool DeletePerson(TPerson p)
{
try
{
unit.PersonRepository.Delete(p);
}
catch (Exception)
{
return false;
}
return true;
}
}
四、通过工作单元,保持操作一致性,手动释放数据上下文
在此将PersonService封装,如果有对多个仓储的操作,封装在一个工作单元中。
(所有与业务逻辑相关的操作在该类实现)
public class PersonManage
{
public IList<PersonVM> GetPersons()
{
using (var unit = new UnitOfWork())
{
var list = new PersonService(unit).Get();
var result = new List<PersonVM>();
foreach (var item in list)
{
result.Add(new PersonVM { Name = item.Name, Age = item.Age, Home = item.Home, PersonID = item.Id });
}
return result;
}
}
public bool AddPerson(PersonVM p)
{
using (var unit = new UnitOfWork())
{
var result = new PersonService(unit).AddPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
unit.Save();
return result;
}
}
public bool DeletePerson(PersonVM p)
{
using (var unit = new UnitOfWork())
{
var result = new PersonService(unit).DeletePerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
unit.Save();
return result;
}
}
public bool EditPerson(PersonVM p)
{
using (var unit = new UnitOfWork())
{
var result = new PersonService(unit).EditPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
unit.Save();
return result;
}
}
}
五、单元测试
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestShow()
{
var res = new PersonManage().GetPersons();
Console.WriteLine(res.Count);
Assert.AreNotEqual(0, res.Count);
}
[TestMethod]
public void TestAdd()
{
var res = new PersonManage().AddPerson(new PersonVM { Home = "meiguo", Age = 11, Name = "tidy" });
Assert.IsTrue(res);
}
[TestMethod]
public void TestEdit()
{
var pmanage = new PersonManage();
var p = pmanage.GetPersons()[0];
p.Name = "fixed";
var res = pmanage.EditPerson(p);
Assert.IsTrue(res);
}
[TestMethod]
public void TestDelete()
{
var pmanage = new PersonManage();
var p = pmanage.GetPersons()[0];
var res = pmanage.DeletePerson(p);
Assert.IsTrue(res);
}
}
小结:
工作单元模式是为了实现业务的事务功能。通过一个数据上下文对相关的仓储操作。但是也不是必须要自己实现模式,通过ORM也可以实现。
如有不对,请多多指教。
仓储(Repository)和工作单元模式(UnitOfWork)的更多相关文章
- 工作单元模式(UnitOfWork)学习总结
工作单元的目标是维护变化的对象列表.使用IUnitOfWorkRepository负责对象的持久化,使用IUnitOfWork收集变化的对象,并将变化的对象放到各自的增删改列表中, 最后Commit, ...
- MVC+EF 理解和实现仓储模式和工作单元模式
MVC+EF 理解和实现仓储模式和工作单元模式 原文:Understanding Repository and Unit of Work Pattern and Implementing Generi ...
- Contoso 大学 - 9 - 实现仓储和工作单元模式
原文 Contoso 大学 - 9 - 实现仓储和工作单元模式 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Micros ...
- 演练5-8:Contoso大学校园管理系统(实现存储池和工作单元模式)
在上一次的教程中,你已经使用继承来消除在 Student 和 Instructor 实体之间的重复代码.在这个教程中,你将要看到使用存储池和工作单元模式进行增.删.改.查的一些方法.像前面的教程一样, ...
- [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)
一.前言 在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现.然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫. ...
- 关于工作单元模式——工作单元模式与EF结合的使用
工作单元模式往往和仓储模式一起使用,本篇文章讲到的是工作单元模式和仓储模式一起用来在ef外面包一层,其实EF本身就是工作单元模式和仓储模式使用的经典例子,其中DbContext就是工作单元,而每个Db ...
- .NET应用架构设计—工作单元模式(摆脱过程式代码的重要思想,代替DDD实现轻量级业务)
阅读目录: 1.背景介绍 2.过程式代码的真正困境 3.工作单元模式的简单示例 4.总结 1.背景介绍 一直都在谈论面向对象开发,但是开发企业应用系统时,使用面向对象开发最大的问题就是在于,多个对象之 ...
- [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ...
- 重新整理 .net core 实践篇—————工作单元模式[二十六]
前言 简单整理一下工作单元模式. 正文 工作单元模式有3个特性,也算是其功能: 使用同一上下文 跟踪实体的状态 保障事务一致性 工作单元模式 主要关注事务,所以重点在事务上. 在共享层的基础建设类库中 ...
随机推荐
- 乘积最大(线性dp)
乘积最大 时间限制: 1 Sec 内存限制: 128 MB提交: 4 解决: 4[提交][状态][讨论版][命题人:quanxing] 题目描述 今年是国际数学联盟确定的“2000——世界数学年” ...
- file does not exist 阿里云OSS图片上传遇到的问题
./uploads\20171209/0497b8dd16e72c6fcf5bfd552f535a81.png file does not exist 原代码 function aliyun($sav ...
- python开发mysql:表关系&单表简单查询
一 一对多,多对一 1.1 建立多对一 ,一对多的关系需要注意 先建立被关联的表,被关联的字段必须保证时唯一的 在创建关联的表,关联的字段一定是可以重复的 1.2 示例: 出版社 多对一,多个老师可能 ...
- Resize事件和SizeChanged事件
窗体加载的时候, 就会触发Form_ResizeBeginForm_ResizeEnd 窗体的拖动会触发:Form_ResizeBeginForm_ResizeEnd 窗体的最小化按钮会触发:Form ...
- python学习(十) 自带电池
10.1 模块 >>> import math >>> math.sin(0) 0.0 10.1.1 模块是程序 假设自己写的hello.py放在c:\python ...
- mybatis~动态SQL(1)
动态SQL MyBatis还有一个方便的功能就是动态SQL,可以根据条件智能生成SQL语句.这里的例子全部来自MyBatis文档. if标签 下面这个例子使用了MyBatis的if元素,在标题不为空的 ...
- python中对 函数 闭包 的理解
最近学到 函数 闭包的时候,似懂非懂.迷迷糊糊的样子,很是头疼,今天就特意查了下关于闭包的知识,现将我自己的理解分享如下! 一.python 闭包定义 首先,关于闭包,百度百科是这样解释的: 闭包是指 ...
- CVE-2017-11882复现配合koadic
项目地址:https://github.com/iBearcat/CVE-2017-11882 首先开启koadic,然后配置一下 复制这句代码 mshta http://192.168.220.13 ...
- Windows nvidia显卡的设置方法
1.直接右下角打开N卡控制面板托盘按钮,一般来说都是自动隐藏在小箭头里的,点开就可以显示隐藏的图标了.[插入图片“右下角小箭头”]2.开始菜单 - 控制面板 ,可以看到“显卡控制面板”这个选项,进去就 ...
- Angular4中利用promise异步加载gojs
GoJS是一个实现交互类图表(比如流程图,树图,关系图,力导图等等)的JS库 gojs提供了angular的基本例子,不过是离线版 https://github.com/NorthwoodsSoftw ...