设计模式/原则篇- Unit of Work
概念
Unit of Work 即工作单元。 用来维护一组受业务影响的对象列表,将多个操作放在一个单元中,把操作原子化,通过事务统一完成一次提交,如果某个过程出现异常,就将所有修改进行回滚,保证数据的有效状态。同时减少了应用程序与数据库通信,有利于提升系统的性能。
具体使用(银行领域的转账建模)
整体项目结构预览
构建UnitOfWork.Infrastructure
1、新建Domain文件夹,添加IAggregateRoot接口
IAggregateRoot接口属于聚合根,所有业务对象(Entity)都需要实现聚合根。外部对象需要访问聚合内的实体时,只能通过聚合根进行访问,而不能直接访问。领域模型需要根据领域概念分成多个聚合,每个聚合都有一个实体作为聚合根,通俗的说,领域对象从无到有的创建,以及CRUD的操作都应该作用于聚合根上,而不是单独的某个实体。
2、新建UnitofWork文件夹,添加IUnitofWorkRepository接口
- void PersistCreationOf(IAggregateRoot entity);
- void PersistUpdateOf(IAggregateRoot entity);
- void PersistDeletionOf(IAggregateRoot entity);
3、添加IUnitofWork接口 注意在添加/修改/删除的方法中传入IUnitofWorkRepository,在提交时Unit Of Work将真正持久化工作放在IUnitofWorkRepository的具体类实现中进行
- void RegisterUpdate(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository);
- void RegisterAdd(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository);
- void RegisterRemove(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)
- void Commit();
构建UnitOfWork.Model
1、新建Account类并实现IAggregateRoot接口 a.定义公共的属性Balace(余额) b.定义带参数(balace) 的构造函数
2、添加IAccountRepository接口,将Account持久化 定义添加/更新/删除方法
3、新建AccoutService类 定义公共的IAccountRepository对象和IUnitOfWork对象,通过其构造器实现依赖注入
- public IAccountRepository _accountRepository;
- public IUnitOfWork _unitOfWork;
- public AccoutService(IAccountRepository accountRepository, IUnitOfWork unitOfWork)
- {
- _accountRepository = accountRepository;
- _unitOfWork = unitOfWork;
- }
- /// <summary>
- /// 转账
- /// </summary>
- /// <param name="from"></param>
- /// <param name="to"></param>
- /// <param name="account"></param>
- public void Transfer(Account from ,Account to ,decimal balace)
- {
- if (from.Balace > balace)
- {
- from.Balace -= balace;
- to.Balace += balace;
- _accountRepository.Save(from);
- _accountRepository.Save(to);
- _unitOfWork.Commit();
- }
- }
构造UnitOfWork.Repository
1、添加AccountRepository类,AccountRepository实现了IAccountRepository和IUnitofWorkRepository接口,IAccountRepository方法的实现简单地将工作委托至Unit Of Work(传入待持久化实体以及Repository的引用),IUnitofWorkRepository方法中实现了真正的持久化任务,具体的持久化方式可以用ADO.NET/EF/NH等。
- public class AccountRepository:IAccountRepository ,IUnitOfWorkRepository
- {
- private IUnitOfWork _unitOfWork;
- public AccountRepository(IUnitOfWork unitOfWork)
- {
- _unitOfWork = unitOfWork;
- }
- public void Save(Account account)
- {
- _unitOfWork.RegisterAdd(account,this);
- }
- public void Update(Account account)
- {
- _unitOfWork.RegisterUpdate(account,this);
- }
- public void Remove(Account account)
- {
- _unitOfWork.RegisterRemove(account,this);
- }
- public void PersistCreationOf(Infrastructure.Domain.IAggregateRoot entity)
- {
- //ADO.NET/EF/NH
- }
- public void PersistUpdateOf(Infrastructure.Domain.IAggregateRoot entity)
- {
- //ADO.NET/EF/NH
- }
- public void PersistDeletionOf(Infrastructure.Domain.IAggregateRoot entity)
- {
- //ADO.NET/EF/NH
- }
- }
2、添加NHUnitOfWork类,实现IUnitofWork接口
- public class NHUnitOfWork : IUnitOfWork
- {
- public Dictionary<IAggregateRoot, IUnitOfWorkRepository> addedEntitys;
- public Dictionary<IAggregateRoot, IUnitOfWorkRepository> changedEntitys;
- public Dictionary<IAggregateRoot, IUnitOfWorkRepository> deletedEntitys;
- public NHUnitOfWork()
- {
- addedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
- changedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
- deletedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
- }
- public void RegisterUpdate(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)
- {
- if (!changedEntitys.ContainsKey(entity))
- {
- changedEntitys.Add(entity,unitOfWorkRepository);
- }
- }
- public void RegisterAdd(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)
- {
- if (!addedEntitys.ContainsKey(entity))
- {
- addedEntitys.Add(entity,unitOfWorkRepository);
- }
- }
- public void RegisterRemove(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)
- {
- if (!deletedEntitys.ContainsKey(entity))
- {
- deletedEntitys.Add(entity,unitOfWorkRepository);
- }
- }
- public void Commit()
- {
- using (TransactionScope scope = new TransactionScope())
- {
- foreach (IAggregateRoot entity in this.addedEntitys.Keys)
- {
- this.addedEntitys[entity].PersistCreationOf(entity);
- }
- foreach (IAggregateRoot entity in this.changedEntitys.Keys)
- {
- this.changedEntitys[entity].PersistUpdateOf(entity);
- }
- foreach (IAggregateRoot entity in this.deletedEntitys.Keys)
- {
- this.deletedEntitys[entity].PersistDeletionOf(entity);
- }
- scope.Complete();
- }
- }
- }
NHUnitOfWork类使用3个字典变量来记录对业务实体的代执行修改。addedEntitys 对应于被添加到数据存储的实体,changedEntitys 处理更新的实体,deletedEntitys处理实体删除,与字典中的实体键匹配的IUnitOfWorkRepository将被保存下来,并用于 Commit 方法之中 ,来调用Repository对象,该对象包含真正持久化实体的代码 。Commit方法遍历每一个字典,并调用相应的IUnitOfWorkRepository方法(传递实体引用)。Commit方法中的工作均被 TransactionScope 代码包装起来,如果在IUnitOfWorkRepository中执行任务时出现异常,则所有工作回滚,数据存储将保持原来的状态。
构建UnitOfWork.Console
1、添加控制台项目用于验证UnitofWork模式效果
- static void Main(string[] args)
- {
- Account a = new Account();
- System.Console.WriteLine("现在a,存有{0}",a.Balace);
- Account b = new Account();
- System.Console.WriteLine("现在b,存有{0}",b.Balace);
- System.Console.WriteLine("a转给b 500元,开始转账......");
- IUnitOfWork unitOfWork = new NHUnitOfWork();
- IAccountRepository accountRepository = new AccountRepository(unitOfWork);
- AccoutService service = new AccoutService(accountRepository,unitOfWork);
- service.Transfer(a,b,);
- System.Console.WriteLine("转账结束");
- System.Console.WriteLine("a当前金额:{0}",a.Balace);
- System.Console.WriteLine("b当前金额:{0}",b.Balace);
- System.Console.ReadKey();
- }
执行后结果如下:
UML类图
总结
对于Unit of Work模式是一种解决方案,一种思路,每个项目的环境都是不一样的,所以我们需要理解的就是其中的原理。以上实例主要是讲解如何部署UnitofWork模式,便于大家的理解,谢谢!
疑问讨论
上面讲的是网上资料中最普遍的说法,对经典的Unit of Work模式进行了说明。但是我个人有一个观点需要和大家探讨一下:
先说一个问题,大家在初次接触Unit of Work的时候,会不会有一个疑问——这个和数据库事务有什么区别?
然后我再说我的观点吧。
1.我觉得Unit of Work就是一个思想,工作单元及原子性操作,而这个关联到数据库中,也就是事务了,所以我觉得数据库事务是Unit of Work在数据库中的一种体现。
2.上面讲述的Unit of Work是网上最常见的一种说法。我个人觉得,上面那只是一种实现方式,而在网上看到大部分关于Unit of Work的优点中“减少数据库连接、减少数据库连接时间”,这些都是这种实现方式的优点。但是我认为这并不是Unit of Work真正的意图,我认为Unit of Work只是单纯的原子操作思想,就像我认为微软提供的TransactionScope也是Unit of Work的一种实现一样。
3.我认为上述的这种普遍的说法可能存在这样的问题:
前置条件:数据库表主键为自增的int型ID。
场景: A表一条数据执行了修改操作,B表执行了一个新增操作,接下来有一个if判断,一条分支是拿B表新增数据的ID调用一个webapi,在之后还有一个对C表的修改操作,另一条分支是在D表中新增一条数据。
问题:因为所有的增删改操作都会被延后到工作单元结束的地方进行执行,所以在代码执行到往B表插入数据的时候,实际上没有插入,这时如果在if判断的地方走了第一个分支,用B表插入的数据ID去调用webapi,这将会出现问题。
解决方案:
1.不适用自增ID,比如换成GUID;
2.为webapi实现一个上述的Unit of Work的适配器,将api操作也压入集合中,等到后面一起处理;
3.在工作单元开始的时候就开启一个数据库事务。
第一个解决方案在一些成型系统中再遇到就是不可行的,第二个方案会稍显麻烦,第三个方案就不存在之前说的“减少数据库连接时间”的优点了。
以上是我个人对Unit of Work的观点与疑问,希望大家能够各发己见,一起探讨一下。
设计模式/原则篇- Unit of Work的更多相关文章
- C#软件设计——小话设计模式原则之:依赖倒置原则DIP
前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”, ...
- C#软件设计——小话设计模式原则之:单一职责原则SRP
前言:上篇C#软件设计——小话设计模式原则之:依赖倒置原则DIP简单介绍了下依赖倒置的由来以及使用,中间插了两篇WebApi的文章,这篇还是回归正题,继续来写写设计模式另一个重要的原则:单一职责原则. ...
- C#软件设计——小话设计模式原则之:开闭原则OCP
前言:这篇继续来看看开闭原则.废话少说,直接入正题. 软件设计原则系列文章索引 C#软件设计——小话设计模式原则之:依赖倒置原则DIP C#软件设计——小话设计模式原则之:单一职责原则SRP C#软件 ...
- 设计模式原则——依赖倒转&里氏代换原则
设计模式一共有六大原则: 单一原则.开放封闭原则.接口分离原则.里氏替换原则.最少知识原则.依赖倒置原则. 这篇博客是自己对依赖倒转&里氏代换原则的一些拙见,有何不对欢迎大家指出. 依赖倒转原 ...
- C#软件设计——小话设计模式原则之:接口隔离原则ISP
前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...
- Java设计模式(二)设计模式原则
学习Java设计模式之前,有必要先了解设计模式原则. 开闭原则 定义 一个软件实体如类.模块和函数应该对扩展开放,对修改关闭 用抽象构建框架,用实现扩展细节 优点:提高软件系统的可复用性及可维护性 C ...
- C++技术问题总结-第12篇 设计模式原则
设计模式六大原则,參见http://www.uml.org.cn/sjms/201211023.asp. 1. 单一职责原则 定义:不要存在多于一个导致类变更的原因.通俗的说,即一个类仅仅负责一项职责 ...
- 设计模式总结篇系列:抽象工厂模式(Abstract Factory)
在上一篇的工厂方法模式中,通过一个公用的类对其他具有相同特性(实现相同接口或继承同一父类)的类的对象进行创建.随之带来的问题在于:当新定义了一个具有相同特性的类时,需要修改工厂类.这与设计模式中的开闭 ...
- DesignPatternPrinciple(设计模式原则)一
设计模式六大原则(1):单一职责原则 定义:不要存在多于一个导致类变更的原因.通俗的说,即一个类只负责一项职责. 问题由来:类T负责两个不同的职责:职责P1,职责P2.当由于职责P1需求发生改变而需 ...
随机推荐
- rabbitmq消息队列——"Hello World!"
RabbitMQ 一."Hello World!" 1.简介: RabbitMQ是一种消息中间件,主要思想很简单:接收消息并转发.你可以将它设想为一个邮局:你往里面发送邮件并确保邮 ...
- CSS3_02之2D、3D动画
1.转换属性:transform:取值:transform-function(转换函数): 2.转换原点:默认元素的中心处:更改转换原点:transform-origin:取值:数字/百分比/关键字: ...
- html_02之表单、其它
1.表单属性action:处理表单数据服务器端处理程序地址,默认提交本页: 2.表单属性method:①get:明文,数据显示地址栏,长度<2KB,向服务器请求数据时使用:②post:密文,提交 ...
- Java基础-接口.编写2个接口:InterfaceA和InterfaceB;在接口InterfaceA中有个方法void printCapitalLetter();在接口InterfaceB中有个方法void printLowercaseLetter();然 后写一个类Print实现接口InterfaceA和InterfaceB,要求 方法 实现输出大写英文字母表的功能,printLowerca
#34.编写2个接口:InterfaceA和InterfaceB:在接口InterfaceA中有个方法void printCapitalLetter():在接口InterfaceB中有个方法void ...
- 【.net】从比较两个字节数组谈起
上午,有位初学者朋友问:如何比较两个字节数组中各字节是否相等? 不许笑,我一向反对嘲笑初学者,初学者不认真学习时你可以批评,但不能讥嘲.你不妨想想,你自己开始学习编程的时候又是什么个光景? 好,于是, ...
- elastic-job
github源码: https://github.com/dangdangdotcom/elastic-job maven中央仓: http://repo1.maven.org/maven2/com/ ...
- Implementation Model Editor of AVEVA in OpenSceneGraph
Implementation Model Editor of AVEVA in OpenSceneGraph eryar@163.com 摘要Abstract:本文主要对工厂和海工设计软件AVEVA的 ...
- Representation Data in OpenCascade BRep
Representation Data in OpenCascade BRep eryar@163.com 摘要Abstract:现在的显示器大多数是光栅显示器,即可以看做一个像素的矩阵.在光栅显示器 ...
- 开发高效的Tag标签系统数据库设计
需求背景 目前主流的博客系统.CMS都会有一个TAG标签系统,不仅可以让内容链接的结构化增强,而且可以让文章根据Tag来区分.相比传统老式的Keyword模式,这种Tag模式可以单独的设计一个Map的 ...
- Java多线程系列--“JUC集合”09之 LinkedBlockingDeque
概要 本章介绍JUC包中的LinkedBlockingDeque.内容包括:LinkedBlockingDeque介绍LinkedBlockingDeque原理和数据结构LinkedBlockingD ...