ABP以AOP的方式实现UnitOfWork功能。通过UnitOfWorkRegistrar将UnitOfWorkInterceptor在某个类被注册到IOCContainner的时候,一并添加到该类在容器中对应的ComponentModel的Interceptors集合中。总结一句话就是,UOW的功能是通过自定义Castle拦截器来实现的。本文主要介绍ABP核心框架中的UnitOfWork的实现,后续会分别介绍ABP其他模块是如何具体实现IUnitOfWork的
如图,AbpKernelModule调用UnitOfWorkRegister的Initialize方法将UnitOfWorkInterceptor拦截器添加到标注了UnitOfWork特性方法的类,以及实现IRepository或IApplicationService的类上。
下图是UnitOfWorkRegister的代码
和通常实现的AOP一样,ABP定义了UnitOfWork特性,方便业务层为方法注入UOW功能。
UnitOfWorkAttribute:用于标注某个方法位UnitOfWork的Attribute类. 通过这个特性,可以指定是否启用UOW,事务隔离级别,TransactionScopeOption等
UnitOfWorkOptions: 封装了UnitOfWork参数的类,其实例是通过UnitOfWorkAttribute的CreateOptions来生成的。
接下来,分析下UnitOfWork是如何封装事务的。
基于接口隔离原则的考量,ABP作者将UnitOfWork的方法分到了三个不同的接口中,如下图。
IUnitOfWorkCompleteHandle:定义了UOW同步和异步的complete方法。实现UOW完成时候的逻辑。
IActiveUnitOfWork:一个UOW除了以上两个接口中定义的方法和属性外,其他的属性和方法都在这个接口定义的。比如Completed,Disposed,Failed事件代理,Filter的enable和disable,以及同步、异步的SaveChanges方法。
IUnitOfWork:继承了上面两个接口。定义了外层的IUnitOfWork的引用和UOW的begin方法。 ABP是通过构建一个UnitOfWork的链,将不同的方法纳入到一个事务中(后文解释)。
UnitOfWorkBase:这个抽象类实现了上面三个接口中定义的方法,而真正实现事务控制的方法是由这个抽象类的子类实现的(比如,真正创建TransactionScope的操作是在EfUnitOfWork,NhUnitOfWork这样的之类中实现的)。UOW中除了事务控制逻辑以外的逻辑都是由UnitOfWorkBase抽象类实现的。
ABP中共有以下4个具体的UOW类型,他们都继承自UnitOfWorkBase。Entity Framework, Nhibernate UnitOfWork是实现事务控制的UOW。MongoDB 和 MemoryDB UnitOfWork是没有事务控制的。 原因很简单,MongoDB本身就没有完整的事务控制功能, 而ABP 框架实现的MemoryDB也是没有事务控制功能的。
IUnitOfWorkManager/UnitOfWorkManager:和其他***manager类一样是一个Facade,他对外提供UOW的功能(用于创建UnitOfWork,并开启UnitOfWork流程),对内调用各种UOW功能的各种组件。
UnitOfWork拦截器调用UnitOfWorkManager开启UOW流程的代码。
Unit Of Work大致运行流程如下
- UOW拦截器被注入到需要UOW的类中。
- ABP执行标注了UnitOfWork特性的方法时。UOW拦截器以begin()->realAction()->complete()->dispose()顺序执行,其中realAction是被调用的真实业务操作。 UOW拦截器是通过using这种方式调用IUnitOfWork的某个具体实现,这就确保begin 和 dispose也总是会被执行的。 这里需要注意complete却不一定会被执行,比如在complete方法被调用前方法的执行产生了异常。
- 当执行一连串的操作时(A方法->B方法->C方法,假设这三个方法都标注了UnitOfWork特性),ABP在执行A方法前会创建整个过程中唯一的IUnitOfWork对象,该对象会启动.NET事务。在执行到B,C方法只会创建InnerUnitOfWorkCompleteHandle。 InnerUnitOfWorkCompleteHandle与IUnitOfWork对象的差异在于它不会创建真实的事务。但ABP会调用其complete,以告知ABP其对应的方法以成功完成,可以提交事务.
- 事务可以回滚的关键关键在于IUnitOfWork对象在被dispose时候会检查complete方法有没有被执行,没有的话就认为这个UOW标注的方法没有顺利完成,从而导致事务的回滚操作。
- 整个事务的提交是通过第一个UOW(也是唯一个)的complete方法执行时提交的。
InnerUnitOfWorkCompleteHandle关于检查complete方法有没有被执行的代码
UnitOfWorkManager的beign方法。这边可以看出只有一个IUnitOfWork对象会被创建, 而且由于这个对象是通过容器直接resolve的,那么ABP怎么知道该通过resolve得到什么样的实例呢?是EfUnitOfWork?还是MongoDbUnitOfWork?还是MemoryDbUnitOfWork?还是NhUnitOfWork?答案是ABP不知道,ABP作者假设你只会用其中一个模块,所以如果你把这四个module都加入到你的项目中,结果是不可预知的。
EfUnitOfWork在begin方法中创建.NET事务
EfUnitOfWork提交事务
CallContextCurrentUnitOfWorkProvider
CallContextCurrentUnitOfWorkProvider的主要功能其实只有一个:通过current返回当前UOW环境下的UOW实例。
一般思路是:将IUnitOfWork对象定义为实例变量或者是类变量。 但是两者事实上都不可行。
如果定义为类变量,那就会面临线程安全的问题,解决方式无非加锁,但会导致并发能力下降,ABP是web框架,因为锁导致并发能力下降是不能接受的。
如果定义为实例变量,在同一线程其他地方resolve CallContextCurrentUnitOfWorkProvider这个实例的时候都会得到一个新的实例,新的实例下current自然是NULL.
ABP的做法是:线程逻辑上下文+线程安全的Dictinoray容器。
线程逻辑上下文用于存储UOW实例的key, 而线程逻辑上下文对于本线程是全局可访问的,而同时具有天然的隔离性。这就确保了当前线程的各个地方都可以得到current的UOW的key
线程安全的Dictinoray容器是一个类实例,用于存放UOW的实例,通过UOW的key就可以取到UOW的实例。
返回ABP源码分析系列文章目录
Q:我有个问题想请教。关于ABP的事务机制。
1.为什么事务要切入所有继承IRepository和IApplicationService的类,这样的话所有的继承类方法都AOP了,虽然实际的事务只有在访问dbcontext的时候UOW才会被初始化,但单查询的方法,ABP也要开启一次事务,是不是非常低效啊。
2.如果我在Appcliation对象中手写事务想缩短事务执行时间,它会不会被ABP的事务机制影响。
A:如果仅仅是读数据,那么可以在方法上加上特性[UnitOfWork(isTransactional: false)]以关闭事务。
Q:这种方式感觉不是很好。
我仔细看了下代码,ABP默认事务的隔离级别是IsolationLevel=ReadUncommitted,作者的意图是不是要给读操作加个nolock而这么实现的啊。因为EF加nolock都是要起事务的。
A:IsolationLevel=ReadUncommitted 不等于给读操作加个nolock。 ABP是通过TransactionScope来控制事务的。没有对EF操作做特殊处理。
A:作者分析的不错,但是ABP得工作单元也是可以实现EF和MongoDb的这种切换的。
A:引用作者分析的不错,但是ABP得工作单元也是可以实现EF和MongoDb的这种切换的。
Q:今天在一个方法中有EF和MongoDb结果一直报错。。。 Unable to cast object of type 'Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork' to type 'Abp.MongoDb.Uow.MongoDbUnitOfWork'.
Q:应该是UnitOfWorkRegistrar而不是UnitOfWorkRegister