UnitOfWork应用
UnitOfWork以及其在ABP中的应用
Unit Of Work(UoW)模式在企业应用架构中被广泛使用,它能够将Domain Model中对象状态的变化收集起来,并在适当的时候在同一数据库连接和事务处理上下文中一次性将对象的变更提交到数据中。
从字面上我们可以我们可以把UnitOfWork叫做工作单元,从概念上它是协助代码块的事务。为什么我们需要用UnitOfWork?有人说EF不是的DbContext的SaveChanges不就有提交变更数据的功能吗?为什么还要多一层封装?是的,如果我们项目只是用EF的话,项目又会经常变更,不用考虑那么多我们可以直接用EF,但是如果我们在支持EF的同时还需要支持Redis、NHibernate或MongoDB呢?我们怎么做统一的事务管理?所以封装一个UnitOfWork是有必要的。类似的Repository也是一样,仓储Repository的功能其实就是EF的DbSet<T>,但是我们的数据库访问技术可能会改变,所以我们需要提供一层封装,来隔离领域层或应用层对数据访问层的依赖。那么ABP是怎么定义UnitOfWork的呢?
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle{ /// <summary> /// Begins the unit of work with given options. /// 开始 unit of work的一些配置UnitOfWorkOptions,主要是事务的级别,超时时间,配置文件等 /// </summary> /// <param name="options">Unit of work options</param> void Begin(UnitOfWorkOptions options);}public interface IUnitOfWorkCompleteHandle : IDisposable{ /// <summary> /// Completes this unit of work. /// It saves all changes and commit transaction if exists. /// 统一事务提交 /// </summary> void Complete(); /// <summary> /// Completes this unit of work. /// It saves all changes and commit transaction if exists. /// 异步的Complete方法 /// </summary> Task CompleteAsync();} |
从接口的定义来看,UnitOfWork主要涉及到事务的提交,回滚操作这边没有再定义一个方法,因为作者用的是TransactionScope,失败了会自动回滚。当然有定义了Dispose方法。现在我们来看下UnitOfWork的实现方法,抽象类UnitOfWorkBase,我删除了一些跟本文无关的代码,方便阅读。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
public abstract class UnitOfWorkBase : IUnitOfWork{ public UnitOfWorkOptions Options { get; private set; } /// <summary> /// 开始UnitOfWork的一些配置,和事务的初始化 /// </summary> /// <param name="options"></param> public void Begin(UnitOfWorkOptions options) { if (options == null) { throw new ArgumentNullException("options"); } PreventMultipleBegin(); Options = options; //TODO: Do not set options like that! SetFilters(options.FilterOverrides); BeginUow(); } /// <summary> /// 事务的提交,异常的捕获 /// </summary> public void Complete() { PreventMultipleComplete(); try { CompleteUow(); _succeed = true; OnCompleted(); } catch (Exception ex) { _exception = ex; throw; } } /// <summary> /// 结束事务,失败就回滚 /// </summary> public void Dispose() { if (IsDisposed) { return; } IsDisposed = true; if (!_succeed) { OnFailed(_exception); } DisposeUow(); OnDisposed(); }} |
我们知道UnitOfWorkBase是抽象类,对于不同的数据访问技术方案我们要定义不用的工作单元实现类,比如EF和NHibernate的事务实现机制是不一样的,这里我们看下EfUnitOfWork
上面已经定义了UnitOfWork接口和实现方法,那我们改怎么使用呢?一般的我们的使用方式是这样的,下面的场景是模拟银行转账功能,从一个账户扣钱和另一个账户加钱。下面是领域层定义的账户转账服务,我们在整个操作实现完后调用 _unitOfWork.Commit()进行提交,在领域服务构造函数注入UnitOfWork。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 账号转账领域服务类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(); } }} |
这样的设计简单易懂,但是我们每个提交都要引用UnitOfWork会比较麻烦,那么有没有更好的设计思路呢?ABP的设计思想还是比较值得借鉴的。ABP的UnitOfWork的设计思路还是沿用作者最喜欢的切面编程,何为切面编程:通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。也就是AOP技术,ABP作者用的是Castle Windsor来实现的。一般的我们需要两步,1、继承IInterceptor接口重写Intercept方法,这样我们就可以实现动态拦截方法了,2、那么我们到底怎么才能动态代理要拦截的方法呢?我们可以继承Attribute,自定义UnitOfWorkAttribute。可能你现在还不明白,那么我们来看下具体代码吧。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
internal class UnitOfWorkInterceptor : IInterceptor{ private readonly IUnitOfWorkManager _unitOfWorkManager; public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager) { _unitOfWorkManager = unitOfWorkManager; } public void Intercept(IInvocation invocation) { if (_unitOfWorkManager.Current != null) { //Continue with current uow invocation.Proceed(); return; } var unitOfWorkAttr = UnitOfWorkAttribute.GetUnitOfWorkAttributeOrNull(invocation.MethodInvocationTarget); if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled) { //No need to a uow invocation.Proceed(); return; } //No current uow, run a new one PerformUow(invocation, unitOfWorkAttr.CreateOptions()); } |
对于Castle Windsor我们只需要像上面的UnitOfWorkInterceptor就是继承IInterceptor重写Intercept就可以实现动态代理啦。下面来看下自定义的UnitOfWorkAttribute。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
[AttributeUsage(AttributeTargets.Method)]public class UnitOfWorkAttribute : Attribute{ /// <summary> /// Is this UOW transactional? /// Uses default value if not supplied. /// </summary> public bool? IsTransactional { get; private set; } /// <summary> /// Timeout of UOW As milliseconds. /// Uses default value if not supplied. /// </summary> public TimeSpan? Timeout { get; private set; } |
好了,定义了UnitOfWorkAttribute,那么我们怎么让它和UnitOfWorkInterceptor结合起来对代码进行动态拦截呢?
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
[UnitOfWork]public virtual async Task<AbpLoginResult> LoginAsync(string userNameOrEmailAddress, string plainPassword, string tenancyName = null){ if (userNameOrEmailAddress.IsNullOrEmpty()) { throw new ArgumentNullException("userNameOrEmailAddress"); } if (plainPassword.IsNullOrEmpty()) { throw new ArgumentNullException("plainPassword"); } using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant)) { TUser user; if (!_multiTenancyConfig.IsEnabled) { using (_unitOfWorkManager.Current.EnableFilter(AbpDataFilters.MayHaveTenant)) { //Log in with default denant user = await FindByNameOrEmailAsync(userNameOrEmailAddress); if (user == null) { return new AbpLoginResult(AbpLoginResultType.InvalidUserNameOrEmailAddress); } } } |
上面代码是利用Attribute的特性对方法进行标识,这是第一步,现在我们已经对要拦截的代码标识了,那么我们是怎么知道它要被拦截的呢?
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
internal static class UnitOfWorkRegistrar{ /// <summary> /// Initializes the registerer. /// </summary>sssss /// <param name="iocManager">IOC manager</param> public static void Initialize(IIocManager iocManager) { iocManager.IocContainer.Kernel.ComponentRegistered += ComponentRegistered; } /// <summary> /// 拦截注册事件 /// </summary> /// <param name="key"></param> /// <param name="handler"></param> private static void ComponentRegistered(string key, IHandler handler) { if (UnitOfWorkHelper.IsConventionalUowClass(handler.ComponentModel.Implementation)) { //判断如果是IRepository和IApplicationService,就注册动态代理 Intercept all methods of all repositories. handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor))); } else if (handler.ComponentModel.Implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(UnitOfWorkHelper.HasUnitOfWorkAttribute)) { //判断如果是被标识了UnitOfWork attribute的就注册动态代理 Intercept all methods of classes those have at least one method that has UnitOfWork attribute. //TODO: Intecept only UnitOfWork methods, not other methods! handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor))); } }} |
请认真看上面的方法ComponentRegistered的代码,UnitOfWorkHelper.HasUnitOfWorkAttribute就是判断是否是UnitOfWorkAttribute。
|
1
2
3
4
|
public static bool HasUnitOfWorkAttribute(MemberInfo methodInfo){ return methodInfo.IsDefined(typeof(UnitOfWorkAttribute), true);} |
你可能会问UnitOfWorkRegistrar的ComponentRegistered方法是什么时候执行的?那么你可以参考下我之前写的Castle Windsor常用介绍以及其在ABP项目的应用介绍 ,关于UnitOfWorkAttribute 是怎么执行的可以参考ABP之模块分析
那么到此我们被标识[UnitOfWork]的登录方法LoginAsync和所有的repositories仓储一旦被执行到就会被拦截,执行我们的代理类。本来这篇文章还想说说仓储的,但是因为篇幅可能会有点长,那就放在下次总结吧,至此UnitOfWork也就总结到此了。
参考文章:
http://www.cnblogs.com/daxnet/archive/2011/06/03/2071931.html
http://www.cnblogs.com/zhili/p/UnitOfWork.html
http://www.cnblogs.com/xishuai/p/3750154.html
UnitOfWork应用的更多相关文章
- 【无私分享:ASP.NET CORE 项目实战(第五章)】Repository仓储 UnitofWork
目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 本章我们来创建仓储类Repository 并且引入 UnitOfWork 我对UnitOfWork的一些理解 UnitOfW ...
- UnitOfWork以及其在ABP中的应用
Unit Of Work(UoW)模式在企业应用架构中被广泛使用,它能够将Domain Model中对象状态的变化收集起来,并在适当的时候在同一数据库连接和事务处理上下文中一次性将对象的变更提交到数据 ...
- MVC+UnitOfWork+Repository+EF 之我见
UnitOfWork+Repository模式简介: 每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性.一致性.解决办法是:在Reposito ...
- 工作单元模式(UnitOfWork)学习总结
工作单元的目标是维护变化的对象列表.使用IUnitOfWorkRepository负责对象的持久化,使用IUnitOfWork收集变化的对象,并将变化的对象放到各自的增删改列表中, 最后Commit, ...
- 开发笔记:用不用UnitOfWork以及Repository返回什么集合类型
这2天实际开发中明确的东西,在这篇博文中记录一下. 之前对是否需要自己封装UnitOfWork有些犹豫,因为Entity Framework就是一个UnitOfWork实现, 自己再封装一下显得有些多 ...
- DDD:Repository和UnitOfWork的生命周期问题
UnitOfWork UnitOfWork是一种有状态的.用例级别的对象.如果不采用ORM是不会使用UnitOfWork模式的, Repository Repository是一种特殊的领域服务,因此是 ...
- [Architect] Abp 框架原理解析(5) UnitOfWork
本节目录 介绍 分析Abp源码 实现UOW 介绍 UOW(全称UnitOfWork)是指工作单元. 在Abp中,工作单元对于仓储和应用服务方法默认开启.并在一次请求中,共享同一个工作单元. 同时在Ab ...
- UnitOfWork机制的实现和注意事项
UnitOfWork机制 /*一点牢骚: * UnitOfWork机制的蛋疼之处: * UnitOfWork机制,决定了插入新的实体前,要预先设置数据库中的主键Id,尽管数据库自己生产主键. * ...
- Linq 与UnitOfWork
submitchages(linq to sql)或者savechanges(ef)的次数是根据你操作方法的数量决定的,也即是:它只认识自己的提交语句(submtchanges,savechanges ...
- 关于 Repository和UnitOfWork 模式的关系
本以为,关于这方面的理解,园子中的文章已经很多的了,再多做文章真的就“多做文章了”,但是最近发现,还是有必要的,首先,每个人对于同一事物的理解方式和出发点都是不同的,所以思考的方式得到结果也是不同的. ...
随机推荐
- 熬之滴水穿石:Spring--精简的J2EE(6)
48--曾用过的View 在Spring MVC架构中View实际上是有多种选择的.JSP是首选的view,实际上在J2E ...
- 部署 Redis 群集
Windows 部署 Redis 群集 1,下载Redis for windows 的最新版本,解压到 c:\Redis 目录下备用https://github.com/MSOpenTech/re ...
- 黄聪:Microsoft Enterprise Library 5.0 系列教程(十) Configuration Application Block
原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(十) Configuration Application Block 到目前为止,我们使用的模块都是在同一个配置 ...
- WPF MediaElement播放器2
在此问个问题,MediaElement播放本地和http上的视频没问题,但是ftp的怎么在本例中播放不了 若高手了解,请不吝赐教 using System; using System.Collecti ...
- ASP.NET 应用程序生命周期
1.请求到达IIS服务器,IIS根据文件后缀找到对应的ISAPI(Internet Server API)扩展来处理,这个配置可在网站属性里的“根目录”选项卡中的“配置”里看到.可以看到,ashx.a ...
- HDU4960Another OCD Patient(间隙dp,后座DP)
Another OCD Patient Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Ot ...
- iOS Crash获取闪回日志和上传server
首先我们整理常常会闪退的异常哪些:数组越界.空引用.引用没有定义方法.内存空间不足等等. 怎样获取crash闪退日志 -- 工具查看 先看第一个问题怎样查看,我搜索的方法有下面几个: 第一个方法:XC ...
- jQuery整理笔记2----jQuery选择整理
一个.基本的选择 1.ID选择器 JavaScript提供了原生方法实如今DOM中选择指定ID值得元素. 使用方法例如以下: var element=document.getElementById(& ...
- JCombobox组合框效果实现(转)
JCombobox是Swing中比较常用的控件,它显示一个项列表,扩展的是ListModel接口的模型,它的显示绘制器通过实现ListCellBenderer接口来绘制列表单元,下面介绍 ①普通应用例 ...
- 【Linux探索之旅】第一部分第二课:下载Linux,免费的噢
内容简介 1.第一部分第二课:下载Linux,免费的噢 2.第一部分第三课预告:测试并安装Ubuntu 下载Linux,免费的噢 大家好,上一课我们认识了非常“霸气侧漏”的Linux操作系统. 也知道 ...