ABP在其内部实现了工作单元模式,统一地进行事务与连接管理。
其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of Work
特性的方法进行操作,在执行完方法之后就会关闭掉工作单元。
一、大致处理流程
1
2
3
4
5
6
7
8
9
10
11
12
13
|
拦截器初始化=>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拦截器首先以这种方式来执行代码:
1
2
3
|
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 调用了其初始化操作。
初始化操作代码如下:
1
2
3
4
5
6
7
|
private void AddInterceptorRegistrars()
{
ValidationInterceptorRegistrar.Initialize(IocManager);
AuditingInterceptorRegistrar.Initialize(IocManager);
UnitOfWorkRegistrar.Initialize(IocManager);
AuthorizationInterceptorRegistrar.Initialize(IocManager);
}
|
在其内部对Castle的注册事件进行了关联,当每一个类型注册到容器当中的时候会触发此事件。
1
2
3
4
5
6
7
8
9
10
|
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()
方法内部针对约束规则对默认实现了IRepository和IApplicationService的类型进行拦截器注入。
HandleTypesWithUnitOfWorkAttribute
方法在新版本的ABP当中则针对实现了UnitOfWorkAttribute
的类型进行拦截器注入,前提是,该类型必须通过Ioc注册,否则不会触发事件。
四、工作单元拦截器
UnitOfWorkInterceptor实现了IInterceptor接口,在调用注册了拦截器的方法的时候,会将其方法拦截下来,去执行该拦截器的Interceptor方法,在Abp当中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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
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方法内部,有两个分支,如果是简单的同步方法,则类似于我们在应用多个时候显式使用工作单元一样,如下:
首先调用Manager,根据options开始一个新的工作单元,执行完拦截的方法体之后,调用工作单元的Complete方法。
但是如果使异步方法的话,则略微复杂一点:
在这里,如果要执行工作单元的方法是一个异步方法的话,我们必须要讲工作但与安的Complete和Dispose放在异步任务当中,保证工作单元会被dispose掉。
在这里,Abp实现了一个异步帮助类,如果是无返回值的情况的话:
则首先执行其原Task,完成后执行传入的异步CompleteAsync方法,并且使用finally,确保无论是否异常都会将uow销毁掉。
而有返回值的方法处理稍微复杂一点,如下:
1
2
3
4
5
6
7
8
9
10
|
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 });
}
|
这里使用反射来调用帮助类的另外一个异步方法,该方法作用类似于无返回值的处理结构:
同样是将Complete与Dispose放在异步方法当中进行执行。
注:关于特性拦截器:
如果在一个Service内部调用自身的private的方法,是无法触发拦截器的。只有当两个不同的service/repositry或者工作单元方法才能够触发被调用者的拦截器。
五、工作单元管理器-IUnitOfWorkManager
该接口的定义如下:
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
|
/// <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()方法。
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
|
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进行了值检测,如果某些项没有赋值,则对其赋予默认值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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只有两个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/// <summary>
/// 用于完成一个工作单元
/// 这个接口不能够被注入或者直接使用
/// 使用 <see cref="IUnitOfWorkManager"/> 代替.
/// </summary>
public interface IUnitOfWorkCompleteHandle : IDisposable
{
/// <summary>
/// 统一事务提交
/// </summary>
void Complete();
/// <summary>
/// 统一事务提交
/// </summary>
Task CompleteAsync();
}
|
这两个方法意思相同,都是用来完成当前工作单元的,且其实现了IDisposable接口,结合IUnitOfWorkManager的using用法就明白其作用。
而内部工作单元InnerUnitOfWorkCompleteHandle,它只实现了IUnitOfWorkCompleteHandle接口,下面是其代码:
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
|
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定义如下:
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
|
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()方法,其定义如下:
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
|
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字典来保证一个线程公用一个单元。
在Abp当中,IUnitOfWorkManager是一个简洁的IUnitOfWork管理对象,而IUnitOfWork工作单元则提供了对整个工作单元所需的所有控制,获取当前工作单元则是通过ICurrentUnitOfWorkProvider来进行控制的,在UnitOfWorkManager当中它的Current属性实际上就是ICurrentUnitOfWorkProvider所提供的Current属性。
ICurrentUnitOfWorkProvider接口定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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框架内部具体的实现类
CallContextCurrentUnitOfWorkProvider当中这个属性的get和set方法的具体实现如下,首先是set方法:
这个方法首先判断,如果Current设置的是null,则表示要退出当前工作单元,其ExitFromCurrentUowScope实现如下:
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
|
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操作则较为简单了:
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
|
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
2
3
4
5
6
7
8
9
10
11
12
13
|
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最外部包裹了一层局部工作单元实现:
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
|
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
例如某个方法,你需要使用工作单元的话,那么如下书写即可:
1
2
3
4
5
6
|
[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会认为这个语句块出现了异常会采取回滚操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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();
}
}
}
|