仓储(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个特性,也算是其功能: 使用同一上下文 跟踪实体的状态 保障事务一致性 工作单元模式 主要关注事务,所以重点在事务上. 在共享层的基础建设类库中 ...
随机推荐
- 多线程设计模式(三):Master-Worker模式
Master-Worker模式是常用的并行模式之一,它的核心思想是,系统有两个进程协作工作:Master进程,负责接收和分配任务:Worker进程,负责处理子任务.当Worker进程将子任务处理完成后 ...
- Mysql提权
获取最高用户root的密码 获取方式:数据库配置文件.端口破解口令.下载数据库文件获取等1.数据库配置文件config,conn,data,sql,include,common,inc等命名文件2.数 ...
- PHP安装使用Zend Opcache扩展
简介 Zend OPCache 的前身是Zend Optimizer + (Zend O+),于 2013年3月中旬改名为 Opcache.其通过 opcode 缓存和优化提供更快的 PHP 执行过程 ...
- Python基本数据类型--列表、元组、字典、集合
一.Python基本数据类型--列表(List) 1.定义:[ ]内以逗号分隔,按照索引,存放各种数据类型,每个位置代表一个元素. 2.列表的创建: # 方式一 list1 = ['name','ag ...
- 201671010140. 2016-2017-2 《Java程序设计》java学习第五周
java学习第五周心得体会 本周,是Java学习第五周,随着时间推移,随着课本内容的推进,我们接触到的程序也开始变得越来越复杂,不再是二三章那些用来练手的小程序了,这一点,在我们的例题运 ...
- 基于C++11的线程池(threadpool),简洁且可以带任意多的参数
咳咳.C++11 加入了线程库,从此告别了标准库不支持并发的历史.然而 c++ 对于多线程的支持还是比较低级,稍微高级一点的用法都需要自己去实现,譬如线程池.信号量等.线程池(thread pool) ...
- UIWidget
[UIWidget] UIWidget在NGUI中的层次如下. 根据上篇所述,UIRect实现实现了Anchor功能.而Widget提供的功能也很简单,如下: 可以看到,widget只提供四个属性,a ...
- 浅谈块元素绝对定位的margin属性
对于div的绝对定位一直以为margin属性是无效的,只是通过top,left,bottom,right定位,然而今天的却发现不是这样的,于是对其做了些实验: 使用的HTML原始测试文件: <! ...
- Dubbo管理中心部署
我们在开发时,需要知道注册中心都注册了哪些服务,以便我们开发和测试.我们可以通过部署一个管理中心来实现.其实管理中心就是一个web应用,部署到tomcat即可. 管理端的部署: 1,首先我们要编译源码 ...
- codeforce468DIV2——D. Peculiar apple-tree
题意给你一颗树,开始时每个结点都有一个小球,每一秒钟每个小球都往上滚一层,当两个球在同一个结点的时候会被消去,如果三个五个七个等在同一个结点的化消去后只剩一个. 分析 这对我来说就TM是英语阅读理解哇 ...