Abp 源码分析(4):Unit of work

一、概述

工作单元是”对象-关系”行为的一种模式。维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决。——《企业应用架构模式》

1、工作单元的作用

工作单元只是将所有修改状态保存下来,在适当的时候在同一数据库连接和事务处理上下文中一次性将对象的变更提交到数据中。额外的优点是既减少了数据库调用次数,又避免数据库长事务。

注意:同一个工作单元一般不要跨线程使用。

2、工作单元和仓储(Repository)模式

工作单元常与仓储(Repository)模式一起使用。因为多个 Repository 可能做出多处更改,如果在 Repository 中调用 DbContext.SaveChangesDbContext.SaveChangesAsync(如果使用 Entity Framework 的话),则产生了多个相互隔离开的事务。

备注:实际上 Entity Framewok 也实现了仓储模式(DbSet)和工作单元模式(DbContext)。

3、工作单元和依赖注入(拦截器)

直接使用工作单元也是可以的。配合依赖注入和拦截器能将工作单元的创建和完成提交的工作自动化,也就是说避免了手工创建工作单元和手工调用 Complete/Commit 。这种自动化使得应对复杂业务逻辑时不用过于关注业务无关的事务安全问题。

二、基础设施

1、UnitOfWork 模块

Abp 的工作单元在 Volo.Abp.Uow 模块中实现。

1
2
3
4
5
6
7
8
9
// File: abp\framework\src\Volo.Abp.Uow\Volo\Abp\Uow\AbpUnitOfWorkModule.cs
public class AbpUnitOfWorkModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
// 针对所有注册入依赖注入容器的服务,都将执行一次 UnitOfWorkInterceptorRegistrar.RegisterIfNeeded 方法。
context.Services.OnRegistred(UnitOfWorkInterceptorRegistrar.RegisterIfNeeded);
}
}

把 UntiOfWork 类(见下文)在依赖容器注册服务时,会为之添加拦截器 UnitOfWorkInterceptor,从而导致为实现类创建代理类(当然,就算不是 UnitOfwork 类但有其他拦截器,也会为实现类创建代理类)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// File: abp\framework\src\Volo.Abp.Uow\Volo\Abp\Uow\UnitOfWorkInterceptorRegistrar.cs
public static class UnitOfWorkInterceptorRegistrar
{
public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
// 如果实现类是 UnitOfWork 类型。
// File: abp\framework\src\Volo.Abp.Uow\Volo\Abp\Uow\UnitOfWorkHelper.cs
if (UnitOfWorkHelper.IsUnitOfWorkType(context.ImplementationType.GetTypeInfo()))
{
// 添加拦截器,将导致为实现类创建代理类。
context.Interceptors.TryAdd<UnitOfWorkInterceptor>();
}
}
}

2、UnitOfWork 类

如果类或类的任意方法标注了 UnitOfWorkAttribute 或类实现了 IUnitOfWorkEnabled 接口,称之为 UntiOfWork 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// File: abp\framework\src\Volo.Abp.Uow\Volo\Abp\Uow\UnitOfWorkHelper.cs
public static bool IsUnitOfWorkType(TypeInfo implementationType)
{
//Explicitly defined UnitOfWorkAttribute
if (HasUnitOfWorkAttribute(implementationType) || AnyMethodHasUnitOfWorkAttribute(implementationType))
{
return true;
}

//Conventional classes
if (typeof(IUnitOfWorkEnabled).GetTypeInfo().IsAssignableFrom(implementationType))
{
return true;
}

return false;
}

3、UnitOfWork 方法

什么是 UnitOfwork 方法?首先,UnitOfwork 方法一定是 UnitOfwork 类中的方法,而 UnitOfWork 类中的方法不一定是 UnitOfwork 方法。如果方法特别标注了 UnitOfWorkAttribute 则根据 UnitOfWorkAttribute 的 IsDisabled 属性判断;否则如果类型标记了 UnitOfWorkAttribute 则根据 UnitOfWorkAttribute 的 IsDisabled 属性判断;如果方法和类都没有标注 UnitOfWorkAttribute,则判断类型是否实现了 IUnitOfWorkEnabled 接口。

备注:在类型和方法都标记了 UnitOfWorkAttribute 情况下,方法的 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
30
31
32
33
34
// File: abp\framework\src\Volo.Abp.Uow\Volo\Abp\Uow\UnitOfWorkHelper.cs
public static bool IsUnitOfWorkMethod([NotNull] MethodInfo methodInfo, [CanBeNull] out UnitOfWorkAttribute unitOfWorkAttribute)
{
Check.NotNull(methodInfo, nameof(methodInfo));

//Method declaration
var attrs = methodInfo.GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Any())
{
unitOfWorkAttribute = attrs.First();
return !unitOfWorkAttribute.IsDisabled;
}

if (methodInfo.DeclaringType != null)
{
//Class declaration
attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Any())
{
unitOfWorkAttribute = attrs.First();
return !unitOfWorkAttribute.IsDisabled;
}

//Conventional classes
if (typeof(IUnitOfWorkEnabled).GetTypeInfo().IsAssignableFrom(methodInfo.DeclaringType))
{
unitOfWorkAttribute = null;
return true;
}
}

unitOfWorkAttribute = null;
return false;
}

如果方法是 UnitOfWork 方法,则应用 UnitOfWork。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// File: abp\framework\src\Volo.Abp.Uow\Volo\Abp\Uow\UnitOfWorkInterceptor.cs
// 同步拦截(也有异步拦截)
public override void Intercept(IAbpMethodInvocation invocation)
{
// 如果不是 UnitOfWork 方法。
// File: abp\framework\src\Volo.Abp.Uow\Volo\Abp\Uow\UnitOfWorkHelper.cs
if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute))
{
invocation.Proceed();
return;
}

// 用工作单元的开启和完成,把方法真正的执行"包"起来。
using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute)))
{
invocation.Proceed();
uow.Complete();
}
}

三、UnitOfWork 的实现

MyZony[Abp vNext 源码分析] - 4. 工作单元 一文中,对 UnitOfWorkManagerUnitOfWork/ChildUnitOfWork 等类及其他基础设施已经做了足够的分析,详见该文。

参考资料

https://uk.wikipedia.org/wiki/Unit_Of_Work
https://aspnetboilerplate.com/Pages/Documents/Unit-Of-Work
https://darkcraft.gitbooks.io/abpdocument2chinese/content/Abp/3.4ABP%E9%A2%86%E5%9F%9F%E5%B1%82-%E5%B7%A5%E4%BD%9C%E5%8D%95%E5%85%83.html
https://www.jianshu.com/p/08d407669740
https://www.cnblogs.com/myzony/p/11112288.html
https://www.cnblogs.com/huaizuo/p/4838680.html
https://ithelp.ithome.com.tw/articles/10157700
https://github.com/Arch/UnitOfWork
https://stackoverflow.com/questions/36691532/dbcontext-savechanges-method-in-unitofwork-or-in-repository-which-is-better
https://www.cnblogs.com/wwh999/p/5458038.html