ABP 工作单元

ABP工作单元

ABP在其内部实现了工作单元模式,统一地进行事务与连接管理。
其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of Work 特性的方法进行操作,在执行完方法之后就会关闭掉工作单元。

一、大致处理流程

拦截器初始化=>start: 拦截器初始化
注入UOW特性=>operation: 注入UOW特性方法
Begin=>operation: Begin()
realAction=>operation: realAction() 真实业务方法
complete=>operation: complete() 执行完成
dispose=>inputoutput: dispose() 销毁并检测
是否销毁条件=>condition: 是否销毁成功?
rollback=>operation: 回退结果
e=>end: 结束

拦截器初始化->注入UOW特性->Begin->realAction->complete->dispose->是否销毁条件
是否销毁条件(yes)->e
是否销毁条件(no)->rollback->e

首先UOW拦截器先被注入到了需要UOW的类当中。
ABP在执行标注了UOW的方法(或者是显示式IUnitOfWorkManager包裹的方法)的时候,UOW拦截器首先以这种方式来执行代码:

A[begin]-->B[realaction_method]
B-->C[complete method]
C-->D[dispose method]

UOW拦截器是通过using这种方式调用IUnitOfWork的某个具体实现,这就确保begin 和 dispose也总是会被执行的。Complete()方法不一定会被执行,比如在complete方法被调用前方法的执行产生了异常。

当执行一连串的操作时(A方法->B方法->C方法,假设这三个方法都标注了UnitOfWork特性),ABP在执行A方法前会调用Begin方法创建整个过程中唯一的IUnitOfWork对象,该对象会启动.NET事务。在执行到B,C方法只会创建InnerUnitOfWorkCompleteHandle。

InnerUnitOfWorkCompleteHandle和IUnitOfWork对象的差异在于它不会创建真实的事务。但ABP会调用其complete,以告知ABP其对应的方法以成功完成,可以提交事务。
事务可以回滚的关键关键在于IUnitOfWork对象在被dispose时候会检查complete方法有没有被执行,没有的话就认为这个UOW标注的方法没有顺利完成,从而导致事务的回滚操作。

整个事务的提交是通过第一个UOW(也是唯一个)的complete方法执行时提交的。

二、文件结构说明

文件名称 说明
UnitOfWorkRegistrar 注册拦截器,实现两种默认的UnitOfWork
UnitOfWorkInterceptor Unit of Work拦截器,实现以AOP的方式进行注入单元控制
IUnitOfWorkManager UnitOfWork管理对象
UnitOfWorkManager IUnitOfWorkManager默认实现
ICurrentUnitOfWorkProvider 当前UnitOfWork管理对象
CallContextCurrentUnitOfWorkProvider ICurrentUnitOfWorkProvider默认实现
IUnitOfWork 工作单元对象(Begin、SaveChanges、Complete、Dispose)
UnitOfWorkBase IUnitOfWork抽象实现类,封装实际操作的前后置操作及异常处理
IActiveUnitOfWork IUnitOfWork操作对象,不包含Begin与Complete操作
IUnitOfWorkCompleteHandle 工作单元完成对象,用于实现继承工作单元功能
InnerUnitOfWorkCompleteHandle IUnitOfWorkCompleteHandle实现之一,用于继承外部工作单元
IUnitOfWorkDefaultOptions UnitOfWork默认设置
UnitOfWorkDefaultOptions IUnitOfWorkDefaultOptions默认实现
UnitOfWorkOptions UnitOfWork配置对象
UnitOfWorkAttribute 标记工作单元的特性
UnitOfWorkFailedEventArgs UnitOfWork的Failed事件参数
UnitOfWorkHelper 工具类
AbpDataFilters 数据过滤相关
DataFilterConfiguration 数据过滤相关

三、注册工作单元

Abp在AbpBootstrapper的private void AddInterceptorRegistrars()方法当中对UnitOfWorkRegistrar 调用了其初始化操作。
初始化操作代码如下:

        private void AddInterceptorRegistrars()
        {
            ValidationInterceptorRegistrar.Initialize(IocManager);
            AuditingInterceptorRegistrar.Initialize(IocManager);
            UnitOfWorkRegistrar.Initialize(IocManager);
            AuthorizationInterceptorRegistrar.Initialize(IocManager);
        }

在其内部对Castle的注册事件进行了关联,当每一个类型注册到容器当中的时候会触发此事件。

        public static void Initialize(IIocManager iocManager)
        {
            iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
            {
                var implementationType = handler.ComponentModel.Implementation.GetTypeInfo();

                HandleTypesWithUnitOfWorkAttribute(implementationType, handler);
                HandleConventionalUnitOfWorkTypes(iocManager, implementationType, handler);
            };
        }

HandleConventionalUnitOfWorkTypes()方法内部针对约束规则对默认实现了IRepositoryIApplicationService的类型进行拦截器注入。
HandleTypesWithUnitOfWorkAttribute方法在新版本的ABP当中则针对实现了UnitOfWorkAttribute的类型进行拦截器注入,前提是,该类型必须通过Ioc注册,否则不会触发事件

四、工作单元拦截器

image_1bqf7eg22ecn4gn7tq10n6asem.png-28.4kB
UnitOfWorkInterceptor实现了IInterceptor接口,在调用注册了拦截器的方法的时候,会将其方法拦截下来,去执行该拦截器的Interceptor方法,在Abp当中UnitOfWorkInterceptor的实现如下:

using System;
using System.Reflection;
using System.Threading.Tasks;
using Abp.Threading;
using Castle.DynamicProxy;

namespace Abp.Domain.Uow
{
    /// <summary>
    /// This interceptor is used to manage database connection and transactions.
    /// </summary>
    internal class UnitOfWorkInterceptor : IInterceptor
    {
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly IUnitOfWorkDefaultOptions _unitOfWorkOptions;

        public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkDefaultOptions unitOfWorkOptions)
        {
            _unitOfWorkManager = unitOfWorkManager;
            _unitOfWorkOptions = unitOfWorkOptions;
        }

        /// <summary>
        /// 拦截方法
        /// </summary>
        /// <param name="invocation">拦截器参数</param>
        public void Intercept(IInvocation invocation)
        {
            MethodInfo method;
            try
            {
                method = invocation.MethodInvocationTarget;
            }
            catch
            {
                method = invocation.GetConcreteMethod();
            }

            // 判断方法是否实现了UOW特性
            var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
            if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
            {
                // 没有实现则直接实行业务方法
                invocation.Proceed();
                return;
            }

            //No current uow, run a new one
            PerformUow(invocation, unitOfWorkAttr.CreateOptions());
        }

        private void PerformUow(IInvocation invocation, UnitOfWorkOptions options)
        {
            // 判断拦截器所拦截到的方法是异步还是同步方法
            if (AsyncHelper.IsAsyncMethod(invocation.Method))
            {
                PerformAsyncUow(invocation, options);
            }
            else
            {
                PerformSyncUow(invocation, options);
            }
        }

        private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
        {
            using (var uow = _unitOfWorkManager.Begin(options))
            {
                invocation.Proceed();
                uow.Complete();
            }
        }

        private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
        {
            // 获取一个工作单元
            var uow = _unitOfWorkManager.Begin(options);

            /* 如果在调用业务方法的时候出现异常,则直接调用uow的Dispose方法。
             * 出现异常的时候,并没有调用Complete方法,所以方法内部的_isCompleteCalled会为false,并且会抛出异常
             */
            try
            {
                invocation.Proceed();
            }
            catch
            {
                uow.Dispose();
                throw;
            }

            // 判断异步方法是否拥有返回值
            if (invocation.Method.ReturnType == typeof(Task))
            {
                invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
                    (Task)invocation.ReturnValue,
                    async () => await uow.CompleteAsync(),
                    exception => uow.Dispose()
                );
            }
            else //Task<TResult>
            {
                invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
                    invocation.Method.ReturnType.GenericTypeArguments[0],
                    invocation.ReturnValue,
                    async () => await uow.CompleteAsync(),
                    exception => uow.Dispose()
                );
            }
        }
    }
}

在拦截器当中,针对拦截到的方法,会判断其是否拥有UOW特性,如果没有且被显式关闭,则不执行工作单元。否则根据其是否是异步方法,分别进行不同的操作。
在PreFormUow方法内部,有两个分支,如果是简单的同步方法,则类似于我们在应用多个时候显式使用工作单元一样,如下:
image_1bqf7hpbt1h802fojm2b48q0r20.png-36.7kB
首先调用Manager,根据options开始一个新的工作单元,执行完拦截的方法体之后,调用工作单元的Complete方法。
但是如果使异步方法的话,则略微复杂一点:
image_1bqf7i2es1ir2vocnca420159r2d.png-79.3kB
在这里,如果要执行工作单元的方法是一个异步方法的话,我们必须要讲工作但与安的Complete和Dispose放在异步任务当中,保证工作单元会被dispose掉。
在这里,Abp实现了一个异步帮助类,如果是无返回值的情况的话:
image_1bqf7id6i1d941ko1q5h170q14j72q.png-50.1kB
则首先执行其原Task,完成后执行传入的异步CompleteAsync方法,并且使用finally,确保无论是否异常都会将uow销毁掉。
而有返回值的方法处理稍微复杂一点,如下:

public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction) 
        { 
            //有返回值的异步任务,要先通过反射来为泛型传值,然后才可调用泛型方法来重写异步返回值 
            return typeof(InternalAsyncHelper) 
                // 获得内部方法 
                .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) 
                // 根据返回值生成泛型方法,由于在拦截器当中获取的是TypeInfo,所以在这里需通过反射方式来调用该方法 
                .MakeGenericMethod(taskReturnType) 
                .Invoke(null, new object[] { actualReturnValue, action, finalAction }); 
        } 

这里使用反射来调用帮助类的另外一个异步方法,该方法作用类似于无返回值的处理结构:
image_1bqf7iots1vghfls1q8r1oq99fd37.png-21kB
同样是将Complete与Dispose放在异步方法当中进行执行。

注:关于特性拦截器:

如果在一个Service内部调用自身的private的方法,是无法触发拦截器的。只有当两个不同的service/repositry或者工作单元方法才能够触发被调用者的拦截器。

五、工作单元管理器-IUnitOfWorkManager

该接口的定义如下:

    /// <summary> 
    /// 工作单元管理器 
    /// 用于开始/控制一个工作单元 
    /// </summary> 
    public interface IUnitOfWorkManager 
    { 
        /// <summary> 
        /// 获取当前活跃的工作单元,如果不存在则为NULL 
        /// </summary> 
        IActiveUnitOfWork Current { get; } 
 
        /// <summary> 
        /// 开始一个新的工作单元 
        /// </summary> 
        /// <returns>一个工作单元的句柄</returns> 
        IUnitOfWorkCompleteHandle Begin(); 
 
        /// <summary> 
        /// 开始一个新的工作单元 
        /// </summary> 
        /// <param name="scope">事务范围选项</param> 
        /// <returns>一个工作单元的句柄</returns> 
        IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope); 
 
        /// <summary> 
        /// 开始一个新的工作单元 
        /// </summary> 
        /// <returns>一个工作单元的句柄</returns> 
        IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options); 
    } 

在ABP当中默认使用UnitOfWorkManager作为IUnitOfWork的实现类,其中Current属性可以直接取得ICurrentUnitOfWork的属性。
在IUnitOfWorkManager接口当中,这三个Begin方法都只是重载,最后都会调用第三个Begin()方法。

public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options) 
        { 
            options.FillDefaultsForNonProvidedOptions(_defaultOptions); 
 
            var outerUow = _currentUnitOfWorkProvider.Current; 
 
            if (options.Scope == TransactionScopeOption.Required && outerUow != null) 
            { 
                return new InnerUnitOfWorkCompleteHandle(); 
            } 
 
            var uow = _iocResolver.Resolve<IUnitOfWork>(); 
 
            uow.Completed += (sender, args) => 
            { 
                _currentUnitOfWorkProvider.Current = null; 
            }; 
 
            uow.Failed += (sender, args) => 
            { 
                _currentUnitOfWorkProvider.Current = null; 
            }; 
 
            uow.Disposed += (sender, args) => 
            { 
                _iocResolver.Release(uow); 
            }; 
 
 
            if (outerUow != null) 
            { 
                options.FillOuterUowFiltersForNonProvidedOptions(outerUow.Filters.ToList()); 
            } 
 
            uow.Begin(options); 
 
             
            if (outerUow != null) 
            { 
                uow.SetTenantId(outerUow.GetTenantId(), false); 
            } 
 
            _currentUnitOfWorkProvider.Current = uow; 
 
            return uow; 
        } 

在本方法中,第一步首先对options进行了值检测,如果某些项没有赋值,则对其赋予默认值:

internal void FillDefaultsForNonProvidedOptions(IUnitOfWorkDefaultOptions defaultOptions) 
{ 
    if (!IsTransactional.HasValue) 
    { 
        IsTransactional = defaultOptions.IsTransactional; 
    } 
 
    if (!Scope.HasValue) 
    { 
        Scope = defaultOptions.Scope; 
    } 
 
    if (!Timeout.HasValue && defaultOptions.Timeout.HasValue) 
    { 
        Timeout = defaultOptions.Timeout.Value; 
    } 
 
    if (!IsolationLevel.HasValue && defaultOptions.IsolationLevel.HasValue) 
    { 
        IsolationLevel = defaultOptions.IsolationLevel.Value; 
    } 
} 

第二步,也是最重要的一步,在方法当中会返回两种不同的对象,一种是实现了IUnitOfWorkCompleteHandle接口的InnerUnitOfWorkCompleteHandle,一种是返回一个实现了IUnitOfWork的全新单元,并且将其设置为当前工作单元。

第一个分支条件判断options的scope是否为Required并且当前也存在了工作单元,那么就会返回一个内部对象。

如果不是的话,则创建一个新的工作单元,并调用该工作单元的Begin方法,且设置此工作单元为当前工作单元。

在这里的IUnitOfWork对象是直接通过容器resolve出来的,这里Abp默认是假设用户会使用一个实现了IUnitOfWork的模块,如果有多个实现的类的话,在这里是不知道会使用哪一个的。

六、内部工作单元-IUnitOfWorkCompleteHandle和InnerUnitOfWorkCompleteHandle

接口IUnitOfWorkCompleteHandle只有两个方法:

    /// <summary> 
    /// 用于完成一个工作单元 
    /// 这个接口不能够被注入或者直接使用 
    /// 使用 <see cref="IUnitOfWorkManager"/> 代替. 
    /// </summary> 
    public interface IUnitOfWorkCompleteHandle : IDisposable 
    { 
        /// <summary> 
        /// 统一事务提交 
        /// </summary> 
        void Complete(); 
 
        /// <summary> 
        /// 统一事务提交 
        /// </summary> 
        Task CompleteAsync(); 
    } 

这两个方法意思相同,都是用来完成当前工作单元的,且其实现了IDisposable接口,结合IUnitOfWorkManager的using用法就明白其作用。
而内部工作单元InnerUnitOfWorkCompleteHandle,它只实现了IUnitOfWorkCompleteHandle接口,下面是其代码:

namespace Abp.Domain.Uow 
{ 
    /// <summary> 
    /// 这个操作适用于方法内部的工作单元块 
    /// 使用inner uow的时候应该显式地调用 <see cref="IUnitOfWorkCompleteHandle.Complete"/> 
    /// 如果你没有调用,那么在UOW的末尾会抛出一个异常来回滚操作 
    /// </summary> 
    internal class InnerUnitOfWorkCompleteHandle : IUnitOfWorkCompleteHandle 
    { 
        public const string DidNotCallCompleteMethodExceptionMessage = "Did not call Complete method of a unit of work."; 
 
        private volatile bool _isCompleteCalled; 
        private volatile bool _isDisposed; 
 
        public void Complete() 
        { 
            _isCompleteCalled = true; 
        } 
 
        public Task CompleteAsync() 
        { 
            _isCompleteCalled = true; 
            return Task.FromResult(0); 
        } 
 
        public void Dispose() 
        { 
            if (_isDisposed) 
            { 
                return; 
            } 
 
            _isDisposed = true; 
 
            if (!_isCompleteCalled) 
            { 
                if (HasException()) 
                { 
                    return; 
                } 
 
                throw new AbpException(DidNotCallCompleteMethodExceptionMessage); 
            } 
        } 
         
        private static bool HasException() 
        { 
            try 
            { 
                return Marshal.GetExceptionCode() != 0; 
            } 
            catch (Exception) 
            { 
                return false; 
            } 
        } 
    } 
} 

这个类的代码相当简单,在Complete与CompleteAsync里面都是简单地将_isCompleteCalled设置为true即可,在Dispose方法当中也仅仅使进行了一些常规性判断,例如是否已经dispose过,是否完成,是否异常等。
而这个内部工作单元并没有执行什么具体的事务操作,因为在之前Manager的判断当中,这个内部对象主要用于工作单元的嵌套,内部工作单元只要保证没有异常被抛出。

七、事务工作单元-IUnitOfWork与IActiveUnitOfWork

实现本接口的工作单元才会在内部实际进行事务与连接管理,其IUnitOfWord定义如下:

namespace Abp.Domain.Uow 
{ 
    /// <summary> 
    /// 定义工作单元 
    /// 这个接口是ABP内部使用的 
    /// 使用 <see cref="IUnitOfWorkManager.Begin()"/> 方法来创建一个新的工作单元。 
    /// </summary> 
    public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle 
    { 
        /// <summary> 
        /// 工作单元的唯一标识 
        /// </summary> 
        string Id { get; } 
 
        /// <summary> 
        /// 外层工作单元 
        /// </summary> 
        IUnitOfWork Outer { get; set; } 
 
        /// <summary> 
        /// 根据指定的选项开始工作单元 
        /// </summary> 
        /// <param name="options">工作单元选项</param> 
        void Begin(UnitOfWorkOptions options); 
    } 
} 

IUnitOfWork继承了IUnitOfWorkCompleteHandle与IActiveUnitOfWork接口,他继承了IUnitOfWorkCompleted接口拥有了Complete方法,而IActiveUnitOfWork接口则主要是提供了过滤器和事件相关的方法、工作单元设置Options以及IsDisposed与同步和异步的SaveChanges()方法,其定义如下:
image_1bqf7k6ib1vkh147hssn76211p944.png-83.4kB
image_1bqf7kcb7njl1jmm1h2ujaa1rhv4h.png-116.8kB

Abp默认实现了这个接口的只有UnitOfWorkBase,而其他具体的事务实现,例如EF,NHibernate等,在Abp具体项目下面的UOW文件夹当中有具体实现。 
这个抽象类主要提供了一些前置、后置工作,以及一些异常处理,在一些方法当中做好前置工作,之后调用抽象方法,将具体实现交给下面子类。例如: 
        /// <inheritdoc/> 
        public void Begin(UnitOfWorkOptions options) 
        { 
            Check.NotNull(options, nameof(options)); 
 
            // 检测是否多次重复调用 
            PreventMultipleBegin(); 
            Options = options; //TODO: Do not set options like that, instead make a copy? 
 
            // 过滤配置 
            SetFilters(options.FilterOverrides); 
 
            SetTenantId(AbpSession.TenantId, false); 
 
            // 子类实现方法 
            BeginUow(); 
        } 
 
        /// <inheritdoc/> 
        public void Complete() 
        { 
            PreventMultipleComplete(); 
            try 
            { 
                CompleteUow(); 
                _succeed = true; 
                OnCompleted(); 
            } 
            catch (Exception ex) 
            { 
                _exception = ex; 
                throw; 
            } 
        } 

在这里两个方法内部都有一个检测重复调用的方法,该方法 内部维护一个bool类型的变量,用于重复调用判断标识,不过在其内部使用的时候并没有用lock来锁住变量进行操作,因为一单给操作加了锁,对于系统来说性能消耗是巨大的,这个时候Abp设计上则使用了线程逻辑上下文(CallContext)+线程安全的Conncurrent.Dictionary字典来保证一个线程公用一个单元。
image_1bqf7mt3jlpf120v3qe37n82o9.png-35kB
在Abp当中,IUnitOfWorkManager是一个简洁的IUnitOfWork管理对象,而IUnitOfWork工作单元则提供了对整个工作单元所需的所有控制,获取当前工作单元则是通过ICurrentUnitOfWorkProvider来进行控制的,在UnitOfWorkManager当中它的Current属性实际上就是ICurrentUnitOfWorkProvider所提供的Current属性。
image_1bqf7n80l1gniqbkfbq1ore94r16.png-35.4kB
ICurrentUnitOfWorkProvider接口定义:

namespace Abp.Domain.Uow 
{ 
    /// <summary> 
    /// Used to get/set current <see cref="IUnitOfWork"/>.  
    /// </summary> 
    public interface ICurrentUnitOfWorkProvider 
    { 
        /// <summary> 
        /// Gets/sets current <see cref="IUnitOfWork"/>. 
        /// Setting to null returns back to outer unit of work where possible. 
        /// </summary> 
        IUnitOfWork Current { get; set; } 
    } 
} 

他只有一个Current属性,在Abp框架内部具体的实现类
image_1bqf7njutv17ro1o841pi15oc1j.png-10.3kB
CallContextCurrentUnitOfWorkProvider当中这个属性的get和set方法的具体实现如下,首先是set方法:
image_1bqf7nqr61om36cekulike1t0j20.png-73.9kB
这个方法首先判断,如果Current设置的是null,则表示要退出当前工作单元,其ExitFromCurrentUowScope实现如下:

private static void ExitFromCurrentUowScope(ILogger logger) 
        { 
            // 根据ContextKey从线程集合中取出当前工作单元key 
            var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string; 
            if (unitOfWorkKey == null) 
            { 
                // 没有取到值,表示当前无工作单元 
                logger.Warn("There is no current UOW to exit!"); 
                return; 
            } 
 
            IUnitOfWork unitOfWork; 
            // UnitOfWorkDictionary类型为ConcurrentDictionary,线程安全字典,用于存储所有工作单元(单线程上最多只能有一个工作单元,但是多线程可能会有多个) 
            if (!UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out unitOfWork)) 
            {  
                // 根据key没有取到value,从线程集合(CallContext)中释放该key 
                CallContext.FreeNamedDataSlot(ContextKey); 
                return; 
            } 
 
            // 从工作单元集合中移除当前工作单元 
            UnitOfWorkDictionary.TryRemove(unitOfWorkKey, out unitOfWork); 
            if (unitOfWork.Outer == null) 
            { 
                // 如果当前工作单元没有外层工作单元,则从线程集合(CallContext)中释放该key 
                CallContext.FreeNamedDataSlot(ContextKey); 
                return; 
            } 
 
            // 这里也就表明了key实际上就是UnitOfWork的Id 
            var outerUnitOfWorkKey = unitOfWork.Outer.Id; 
 
            if (!UnitOfWorkDictionary.TryGetValue(outerUnitOfWorkKey, out unitOfWork)) 
            { 
                // 如果当前工作单元有外层工作单元,但是从工作单元集合中没有取到了外层工作单元,那么同样从线程集合(CallContext)中释放该key 
                CallContext.FreeNamedDataSlot(ContextKey); 
                return; 
            } 
            // 能到这里,就表示当前工作单元有外层工作单元,并且从工作单元集合中获取到了外层工作单元,那么就设外层工作单元为当前工作单元 
            CallContext.LogicalSetData(ContextKey, outerUnitOfWorkKey); 
        } 

如果是正常设置Current属性的话,则先获取当前工作单元的Key,如果存在Key的话,则尝试从工作单元集合当中获取这个单元,如果获得的单元与要设置的单元相同的话,则直接返回,不做任何操作,不是的话,则将当前工作单元作为本次所设置的工作单元的外部单元。
后续操作很简单,在工作单元集合当中根据要设置的工作单元ID作为Key在集合当中插入元素,添加失败,抛出异常,否则设置当前工作单元的Key为设置的最新的unitOfWorkKey。
而Get操作则较为简单了:

private static IUnitOfWork GetCurrentUow(ILogger logger) 
        { 
            // 获取当前工作单元key 
            var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string; 
            if (unitOfWorkKey == null) 
            { 
                return null; 
            } 
 
            IUnitOfWork unitOfWork; 
            if (!UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out unitOfWork)) 
            { 
  // 如果根据key获取不到当前工作单元,那么就从当前线程集合(CallContext)中释放key 
                CallContext.FreeNamedDataSlot(ContextKey); 
                return null; 
            } 
 
            if (unitOfWork.IsDisposed) 
            { 
  // 如果当前工作单元已经dispose,那么就从工作单元集合中移除,并将key从当前线程集合(CallContext)中释放 
                logger.Warn("There is a unitOfWorkKey in CallContext but the UOW was disposed!"); 
                UnitOfWorkDictionary.TryRemove(unitOfWorkKey, out unitOfWork); 
                CallContext.FreeNamedDataSlot(ContextKey); 
                return null; 
            } 
 
            return unitOfWork; 
        } 

总的来说,所有的工作单元都存储在线程安全的字典对象当中,每个主线程共用一个工作单元实现。

八、关于工作单元嵌套使用解析/异步-同步方法

1.API Action 默认包裹了一个EF UOW。

2.在最外层Action进行最后持有事务的UOW进行Complete操作,并且在using结束后会调用Dispose方法进行异常检测。

1=>start: 最外层Action UOW
2=>operation: 内部UOW(N1 UOW)
3=>operation: 调用Dispose进行完成检查
4=>operation: 内部UOW(N2 UOW)
5=>operation: 调用Dispose进行完成检查
6=>condition: N2是否完成?
6x=>condition: N1是否完成?
7=>end
8=>operation: 回滚操作

1->2->3->4->5->6
6(yes)->7
6(no)->8

3.AbpUowActionFilter最外部包裹了一层局部工作单元实现:

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            if (!context.ActionDescriptor.IsControllerAction())
            {
                await next();
                return;
            }

            var unitOfWorkAttr = _unitOfWorkDefaultOptions
                .GetUnitOfWorkAttributeOrNull(context.ActionDescriptor.GetMethodInfo()) ??
                _aspnetCoreConfiguration.DefaultUnitOfWorkAttribute;

            if (unitOfWorkAttr.IsDisabled)
            {
                await next();
                return;
            }

            using (var uow = _unitOfWorkManager.Begin(unitOfWorkAttr.CreateOptions()))
            {
                var result = await next();
                if (result.Exception == null || result.ExceptionHandled)
                {
                    await uow.CompleteAsync();
                }
            }
        }

在其内部,判断方法调用的时候是否抛出异常决定是否进行回滚操作。
只要内部工作单元没有调用Complete也会抛出异常被其捕获,如果没有出现异常会使用持有事务的UOW进行统一提交,否则抛出异常信息。

九、使用工作单元

1.使用UnitOfWork Attribute

例如某个方法,你需要使用工作单元的话,那么如下书写即可:

   [UnitOfWork]
   public void CreatePerson(CreatePersonInput input) {
      var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
      _personRepository.Insert(person);
      _statisticsRepository.IncrementPeopleCount();
   }

因此,CreatePerson方法转变成工作单元并且管理数据库连接和事务,两个仓储对象都使用相同的工作单元。要注意,假如这是应用服务的方法则不需要添加UnitOfWork属性。

2.使用IUnitOfWorkManager.Begin()

UnitOfWork实际上也是采用的这种方式,你可以通过这种方式来创建有限范围的工作单元,这种机制当中你可以手动调用Complete()方法,如果你不调用,通过前面对UOW实现的了解,那么Manager会认为这个语句块出现了异常会采取回滚操作。

public class MyService {
      private readonly IUnitOfWorkManager _unitOfWorkManager;
      private readonly IPersonRepository _personRepository;
      private readonly IStatisticsRepository _statisticsRepository;

      public MyService(IUnitOfWorkManager unitOfWorkManager, IPersonRepository personRepository, IStatisticsRepository statisticsRepository) {
         _unitOfWorkManager = unitOfWorkManager;
         _personRepository = personRepository;
         _statisticsRepository = statisticsRepository;
      }

      public void CreatePerson(CreatePersonInput input) {
         var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
         using(var unitOfWork = _unitOfWorkManager.Begin()) {
            _personRepository.Insert(person);
            _statisticsRepository.IncrementPeopleCount();
            unitOfWork.Complete();
         }
      }
   }