# [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)
## 一、前言
在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现。然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫。
## 二、什么是工作单元模式(Unit Of Work)
工作单元模式:用来维护一个已经被业务事务修改(包括添加、修改或更新)的业务对象列表。工作单元模式复制协调这些修改的持久化工作以及所有标记的并发问题。采用工作单元模式带来的好处是能够保证数据的完整性。如果在持久化一系列业务对象的过程中出现问题,则将所有的修改回滚,以保证数据始终处于有效状态。
简单来说,工作单元模式就是把业务对象的持久化由工作单元实现类进行统一管理。而不想之前那样,分布在每个具体的仓储类中,这样就可以达到一系列业务对象的统一管理,不至于在每个业务对象中出现统一的提交和回滚业务逻辑,实现代码最大化重用。
## 三、工作单元模式的实现
从工作单元模式的定义可以看出,工作单元需要保存被业务事务修改的业务对象列表,则必须定义3个集合,分别是添加、修改和更新集合,然而如果使用EF的话,则不需要了,因为DbContext.DbSet<T>就可以代替这三个集合。这里为了演示,我们并没有引入EF,所以我们实现中定义了3个集合来保存被修改的业务对象。既然要在工作单元类中进行统一管理,则我们就需要在工作单元类中定义一个Commit方法,该方法的实现就是去遍历这三个集合的对象,对它们进行统一提交,如果其中一个失败,则进行数据回滚。根据面向接口编程原则,我们需要定义一个工作单元接口,即IUnitOfWork接口。经过上面的分析,再结合下面具体的实现来理解,工作单元模式的实现也就更加清晰了,下面让我们一起去实现下工作单元模式。
**第一步:我们需要定义我们的领域层。**
这里以银行账号之间转账业务作为背景,自然涉及到银行账号业务对象。并且领域层同时包括仓储接口的定义和领域服务。
领域服务指的是:如果有些方法涉及多个实体或聚合的交互,此时就应该把这段逻辑放到领域服务中,领域服务只有方法没有属性,也就是没有状态的。在银行转账业务中,账户之间的转账操作就适合作为领域服务来提供,因为转账操作涉及多个聚合间的交互,需要从一个账户扣钱和另一个账户加钱。所以在领域层还需要定义账户转账服务。经过这样的分析,则领域层的实现如下所示:
```
// 账号仓储接口
public interface IAccountRepository
{
void Save(Account account);
void Add(Account account);
void Remove(Account account);
}
// 账号实体类
public class Account : IAggregateRoot
{
public decimal Balance { get; set; }
public System.Guid Id { get; set; }
public Account()
{
Id = Guid.NewGuid();
}
}
// 账号转账领域服务类
public class AccountService
{
private readonly IAccountRepository _productRepository;
private readonly IUnitOfWork _unitOfWork;
public AccountService(IAccountRepository productRepository, IUnitOfWork unitOfWork)
{
_productRepository = productRepository;
_unitOfWork = unitOfWork;
}
public void Transfer(Account from, Account to, decimal amount)
{
if (from.Balance >= amount)
{
from.Balance -= amount;
to.Balance += amount;
_productRepository.Save(from);
_productRepository.Save(to);
_unitOfWork.Commit();
}
}
}
```
** 第二步:构建基础设施层**
我们一般把工作单元模式的实现放在基础设施层,因为工作单元模式属于一种技术支持。根据上面工作单元模式的分析,我们首先定义IUnitOfWork接口,接着定义它的实现。因为这里没有引入EF,所以具体的实体的持久化还是调用具体的仓储来实现持久化的,所以还需要定义一个IUnitOfWorkRepository接口。则基础设施层的实现如下所示:
```
// 工作单元接口
public interface IUnitOfWork
{
void RegisterAmended(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void RegisterNew(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void RegisterRemoved(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void Commit();
}
// 工作单元实现
public class UnitOfWork : IUnitOfWork
{
// 引入了EF就不需要额外定义三个列表了,因为EF框架中包含的DbContext.DbSet<T>可以记录这3个列表
// 然而在ByteartRetail案例中,也定义这3个列表,但其没有被真真使用到
private readonly Dictionary<IAggregateRoot, IUnitOfWorkRepository> _addedEntities;
private readonly Dictionary<IAggregateRoot, IUnitOfWorkRepository> _changedEntities;
private readonly Dictionary<IAggregateRoot, IUnitOfWorkRepository> _deletedEntities;
public UnitOfWork()
{
_addedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
_changedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
_deletedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
}
// 将业务对象实体添加到内部列表中,真正完成实体持久化操作的还是由具体的仓储类去完成
public void RegisterAmended(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository)
{
if (!_changedEntities.ContainsKey(entity))
{
_changedEntities.Add(entity, unitofWorkRepository);
}
}
public void RegisterNew(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository)
{
if (!_addedEntities.ContainsKey(entity))
{
_addedEntities.Add(entity, unitofWorkRepository);
};
}
public void RegisterRemoved(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository)
{
if (!_deletedEntities.ContainsKey(entity))
{
_deletedEntities.Add(entity, unitofWorkRepository);
}
}
protected void ClearRegisterations()
{
_addedEntities.Clear();
_changedEntities.Clear();
_deletedEntities.Clear();
}
// 对内部列表进行统一提交
// 引入EF后,提交的实现有点不同,它具体的持久化只需要调用DbContext.SaveChanges方法来完成
// 则具体的仓储接口不需要实现IUnitOfWorkRepository接口,则自然不存在IUnitOfWorkRepository接口的定义
public void Commit()
{
// 事务范围
using (var scope = new TransactionScope())
{
// 分别调用具体的仓储对象的持久化逻辑来对业务对象进行持久化
foreach (var entity in this._addedEntities.Keys)
{
this._addedEntities[entity].PersistCreationOf(entity);
}
foreach (var entity in this._changedEntities.Keys)
{
this._changedEntities[entity].PersistUpdateOf(entity);
}
foreach (var entity in this._deletedEntities.Keys)
{
this._deletedEntities[entity].PersistDeletionOf(entity);
}
scope.Complete();
}
// 清楚内存中对象
ClearRegisterations();
}
}
public interface IUnitOfWorkRepository
{
Hashtable AccountList { get; }
void PersistCreationOf(IAggregateRoot entity);
void PersistUpdateOf(IAggregateRoot entity);
void PersistDeletionOf(IAggregateRoot entity);
}
public interface IAggregateRoot
{
Guid Id { get; }
}
```
**第三步:实现仓储层。**
仓储的实现在之前也说过,它其实可以放在基础设施层里,但一般总将其放在一个单独层进行实现。所以这里也就放在一个单独层进行实现。这里仓储实现只有一个类,即对IAccountRepository接口的实现。具体的实现代码如下所示:
```
public class AccountRepository : IAccountRepository, IUnitOfWorkRepository
{
private readonly IUnitOfWork _unitOfWork;
public AccountRepository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
AccountList = new Hashtable();
}
public Hashtable AccountList { get; set; }
#region IAccountRepository Members
public void Save(Account account)
{
_unitOfWork.RegisterAmended(account, this);
}
public void Add(Account account)
{
_unitOfWork.RegisterNew(account, this);
}
public void Remove(Account account)
{
_unitOfWork.RegisterRemoved(account, this);
}
#endregion
#region IUnitOfWorkRepository Members
public void PersistUpdateOf(IAggregateRoot entity)
{
// ADO.net code to update the entity...
// 这里为了演示,只它持久化到内存中
if (AccountList.ContainsKey(entity.Id))
{
AccountList[entity.Id] = entity;
}
}
public void PersistCreationOf(IAggregateRoot entity)
{
// ADO.net code to Add the entity...
// 这里为了演示,只它持久化到内存中
AccountList.Add(entity.Id, entity);
}
public void PersistDeletionOf(IAggregateRoot entity)
{
// ADO.net code to delete the entity...
// 这里为了演示,只它持久化到内存中
if (AccountList.ContainsKey(entity.Id))
{
AccountList.Remove(entity.Id);
}
}
#endregion
}
```
这样,就完成了工作单元模式模式的实现和应用了,工作单元模式的实现存在于基础设施层,其他层的构建主要为了演示,下面通过一个测试项目来进行测试下工作单元的使用。具体项目的引入将会在下一个专题中介绍,下一个专题将引入工作单元模式和规约模式的实现。具体的测试项目的代码如下所示:
```
[TestClass]
public class AccountRepositoryTests
{
[TestMethod]
public void AccountRepository_Delegates_Changes_To_The_Unit_Of_Work_Instance()
{
var accountToBeAmended = new Account();
var accountToBeRemoved = new Account();
var accountToBeAdded = new Account();
// 需要引入Moq Mock框架
var unitOfWorkMockery = new Mock<IUnitOfWork>();
var accountRepository = new AccountRepository(unitOfWorkMockery.Object);
unitOfWorkMockery.Setup(uow => uow.RegisterAmended(accountToBeAmended, accountRepository));
unitOfWorkMockery.Setup(uow => uow.RegisterNew(accountToBeAdded, accountRepository));
unitOfWorkMockery.Setup(uow => uow.RegisterRemoved(accountToBeRemoved, accountRepository));
accountRepository.Add(accountToBeAdded);
accountRepository.Save(accountToBeAmended);
accountRepository.Remove(accountToBeRemoved);
unitOfWorkMockery.VerifyAll();
}
}
```
## 四、总结
到这里,本专题的内容就介绍完了,上一专题和这一专题都是一个前期准备的专题,主要是为网上书店案例引入这2个模式的实现做一个铺垫,为了让大家对知识点分开吸收,然后再通过后面一专题的应用来加深理解这2个模式的应用。
本专题的所有源码下载:[UnitOfWorkDemo.zip](http://files.cnblogs.com/files/zhili/UnitOfWorkDemo.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结