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

其整体流程大概如下:

  • 首先 UOW 相关接口、拦截器等通过 IocManager 注入到 Ioc 容器当中。
  • 监听 Ioc 注册事件,并为其添加方法拦截器。
  • 在拦截器内部使用 using 包裹数据库操作方法,使其成为一个工作单元。
  • 一旦在方法 procced 执行的时候,产生任何异常触发任何异常都不会执行 Complete 方法,直接抛出终止执行。

UnitOfWorkInterceptors

这是一个 Castle Interceptors 的实现,在 AbpBootStrap 的 Initialze 方法当中被注入到 Ioc 容器。

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

之后在 Registrar 内部针对组件注入事件进行绑定:

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);
};
}

在这里的两个方法分别是针对已经实现了 UnitOfWork 特性的类进行绑定,另外一个则是针对符合命名规则的类型进行拦截器绑定。

拦截器做的事情十分简单,针对拦截到的方法进行 UOW 特性检测,如果检测通过之后则直接执行工作单元方法,并根据特性生成 Options。不过不是一个 UOW 的话,则直接继续执行原先方法内代码。

var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
{
//No need to a uow
invocation.Proceed();
return;
}
PerformUow(invocation, unitOfWorkAttr.CreateOptions());

在创建 UOW 的时候,拦截器也会根据方法类型的不同来用不同的方式构建 UOW 。

如果是同步方法的话:

private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
// 直接从 Manager 生成一个新的工作单元
using (var uow = _unitOfWorkManager.Begin(options))
{
// 继续执行原方法
// 产生异常直接进入 uow 的 Dispose 方法
invocation.Proceed();
// 如果一切正常,提交事务
uow.Complete();
}
}

但如果这个工作单元被标注在异步方法上面,则操作略微复杂:

private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
// 获得一个工作单元
var uow = _unitOfWorkManager.Begin(options);
// 继续执行拦截到的方法
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()
);
}
}

以上代码在进入的时候直接执行原方法,如果产生任何异常直接进入 Dispose 方法并且抛出异常。乍一看与同步方法处理没什么区别,但重点是这里并没有执行 Complete 方法,因为这里需要对其异步返回结果更改为 UOW 异步提交之后的值,先查看第一种直接返回 Task 的情况.

public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
try
{
// 原有异步任务返回值
await actualReturnValue;
// 新的异步返回结果
await postAction();
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}

在内部首先等待原有任务执行完成之后再执行传入的 UOW 的 CompleteAsync() 方法,并且在执行过程中有无异常都会直接调用 UOW 的 Disopse 释放资源。

第二种则适用于有返回值的情况:

public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction)
{
// 获得 AwaitTaskWithPreActionAndPostActionAndFinallyAndGetResult 方法,并且通过反射构造一个泛型方法,并且将自身参数传入调用。
return typeof (InternalAsyncHelper)
.GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(taskReturnType)
.Invoke(null, new object[] { actualReturnValue, action, finalAction });
}
public static async Task<T> AwaitTaskWithPreActionAndPostActionAndFinallyAndGetResult<T>(Func<Task<T>> actualReturnValue, Func<Task> preAction = null, Func<Task> postAction = null, Action<Exception> finalAction = null)
{
Exception exception = null;
try
{
if (preAction != null)
{
await preAction();
}
var result = await actualReturnValue();
if (postAction != null)
{
await postAction();
}
return result;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
if (finalAction != null)
{
finalAction(exception);
}
}
}

这两个方法的作用都是确保 CompleteAsync 和 Dispose 能够在放在异步任务当中执行。

IUnitOfWorkManager

顾名思义,这是一个 UOW 的管理器,在 ABP 内部有其一个默认实现 UnitOfWorkManager,在 AbpKernel 模块初始化的时候就已经被注入。

核心方法是 Begin 方法,在 Begin 方法当中通过 FillDefaultsForNonProvidedOptions 方法判断是否传入了配置参数,如果没有传入的话则构建一个默认参数对象。

public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
{
// 如果没有 UOW 参数,构造默认参数
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);
};
//Inherit filters from outer UOW
if (outerUow != null)
{
options.FillOuterUowFiltersForNonProvidedOptions(outerUow.Filters.ToList());
}
uow.Begin(options);
//Inherit tenant from outer UOW
if (outerUow != null)
{
uow.SetTenantId(outerUow.GetTenantId(), false);
}
// 设置当前工作单元为新创建的 UOW
_currentUnitOfWorkProvider.Current = uow;
return uow;
}

这里 Begin 方法所返回的是一个 IUnitOfWorkCompleteHandle 对象,跳转到 IUnitOfWorkCompleteHandle,可以看到它有两个方法:

public interface IUnitOfWorkCompleteHandle : IDisposable
{
void Complete();
Task CompleteAsync();
}

都是完成工作单元的方法,一个同步、一个异步,同时这个接口也实现了 IDispose 接口,从开始使用 using 就可以看出其含义。

InnerUnitOfWorkCompleteHandle

参考其引用,可以发现有一个 IUnitOfWork 接口继承自它,并且还有一个 InnerUnitOfWorkCompleteHandle 的内部实现。这里先查看 InnerUnitOfWorkCompleteHandle 内部实现:

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 的同步和异步方法都只是对完成标识进行一个标记。并未真正的进行任何数据库事务操作。同时在他的内部也实现了 IDispose 接口,如果 complete 未被标记为已完成,那么直接抛出异常,后续操作不会执行。

现在再转到 Begin 方法内部就可以发现,在创建的时候会首先判断了当前是否已经存在了工作单元,如果存在了才会创建这样一个内部工作单元。也就是说真正那个的事务操作是在返回的 IUnitOfWork 当中实现的。

这样的话就会构建出一个嵌套的工作单元:

OuterUOW->InnerUow1->InnerUOW2->.....->InnerUowN

你可以想象有以下代码:

public void TestUowMethod()
{
using(var outerUOW = Manager.Begin()) // 这里返回的是 IOC 解析出的 IUnitOfWork
{
OperationOuter();
using(var innerUOW1 = Manager.Begin()) // 内部 UOW
{
Operation1();
using(var innerUOW2 = Manager.Begin()) // 内部 UOW
{
Operation2();
Complete();
}
Complete();
}
Complete();
}
}

当代码执行的时候,如同俄罗斯套娃,从内部依次到外部执行,内部工作单元仅会在调用 Complete 方法的时候将 completed 标记为 true,但一旦操作抛出异常,Complete无法得到执行,则会直接抛出异常,中断外层代码执行。

那么 IUnitOfWork 的实现又是怎样的呢?

在 ABP 内部针对 EF Core 框架实现了一套 UOW,其继承自 UnitOfWorkBase,而在 UnitOfWorkBase 内部有部分针对接口 IActiveUnitOfWork 的实现,同时由于 IUnifOfWork 也实现了 IUnitOfWorkCompleteHandle 接口,所以在 Begin 方法处能够向上转型。

IActiveUnitOfWork

在 IActiveUnitOfWork 接口当中定义了工作单元的三个事件,用户在使用的时候可以针对这三个事件进行绑定。

事件名称 触发条件
Completed 工作单元调用 Complete 方法之后触发
Failed 工作单元在调用 Complete 方法如果产生异常,则会在 Dispose 释放资源时触发。
Disposed 释放工作单元的时候触发

除了三个事件之外,在 ABP 当中的数据过滤器也是在工作单元调用的时候工作的,后面讲解 EfCoreUnitOfWork 的时候会研究,数据过滤仅包括软删除等审计字段,同时也包括多租户的租户 ID 也在工作单元当中进行设置的。

在这里也定义了两个 SaveChanges 方法,一个同步、一个异步,作用跟实体上下文的同名方法作用差不多。

IUnitOfWork

IUnitOfWork 同时继承了 IUnitOfWorkCompleteHandle 与 IActiveUnitOfWork 接口,并且增加了两个属性与一个 Begin 方法。

UnitOfWorkBase

UnitOfWorkBase 是所有工作单元的一个抽象基类,在其内部实现了 IUnitOfWork 的所有方法,其中也包括两个父级接口。

下面就一个一个属性讲解。

Id

这个 Id 是在构造函数当中初始化的,全局唯一的 Id,使用 Guid 初始化其值。

Outer

用于标明当前工作单元的外部工作单元,其值是在 UnitOfWorkManager 创建工作单元的时候赋予的。在 Manager 的 Begin 方法当中每次针对 Current Uow 赋值的时候都会将已经存在的 UOW 关联最新的 Current Uow 的 Outer 属性上面。形成如下结构:

Current Uow = Uow3.Outer = Uow2.Outer = Uow1.Outer = null

具体代码参考 ICurrentUnitOfWorkProvider 的实现。

Begin()

在 Manager 创建好一个 Uow 之后,就会调用它的 Begin 方法,在内部首先做了一个判断,判断是否多次调用了 Begin 方法,这里可以看到并未做加锁处理。这是因为在 ABP 当中,一个线程当中共用一个工作单元。其实在 CurrentProvider 的代码当中就可以看出来如果在一个线程里面创建了多个工作单元,他会将其串联起来。

之后设置过滤器,并且开始调用 BeginUow 方法,这里并未实现,具体实现我们转到 EfUnitOfWork 可以看到。

BeginUow()-EfCoreUnitOfWork

protected override void BeginUow()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.InitOptions(Options);
}
}

覆写了父类方法,仅对设置进行初始化。

其实到这里我们就大概清楚了 ABP 整个 UOW 的工作流程,如果是两个打上 UnitOfWork 特性的方法在 A 调用 B 的时候其实就会封装成两个嵌套的 using 块。

using(var a = Manager.Begin())
{
// 操作
using(var b = Manager.Begin())
{
// 操作
b.Complete();
}
a.Complete();
}

而最外层的 Complete 就是真正执行了数据库事务操作的。

可以看下 EfUnitOfWork 的实现:

// 这里是 UnitOfWorkBase 的 Complete 方法
public void Complete()
{
PreventMultipleComplete();
try
{
CompleteUow();
_succeed = true;
OnCompleted();
}
catch (Exception ex)
{
_exception = ex;
throw;
}
}
// 这里是 EfUnitOfWork 的 CompleteUow 方法
protected override void CompleteUow()
{
SaveChanges();
CommitTransaction();
}
// 遍历所有激活的 DbContext 并保存更改到数据库
public override void SaveChanges()
{
foreach (var dbContext in GetAllActiveDbContexts())
{
SaveChangesInDbContext(dbContext);
}
}
// 提交事务
private void CommitTransaction()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.Commit();
}
}

[ABP]浅谈工作单元 在整个 ABP 框架当中的应用的更多相关文章

  1. 浅谈工作单元 在整个 ABP 框架当中的应用

    ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...

  2. ABP(现代ASP.NET样板开发框架)系列之12、ABP领域层——工作单元(Unit Of work)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ABP是“ASP.NET Boilerplate Pr ...

  3. ABP理论学习之工作单元(Unit of Work)

    返回总目录 本篇目录 公共连接和事务管理方法 ABP中的连接和事务管理 仓储类 应用服务 工作单元 工作单元详解 关闭工作单元 非事务的工作单元 工作单元方法调用其它 工作单元作用域 自动保存 IRe ...

  4. ABP领域层——工作单元(Unit Of work)

    ABP领域层——工作单元(Unit Of work) 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ...

  5. [ABP]浅谈模块系统与 ABP 框架初始化

    在 ABP 框架当中所有库以及项目都是以模块的形式存在,所有模块都是继承自AbpModule 这个抽象基类,每个模块都拥有四个生命周期.分别是: PreInitialze(); Initialize( ...

  6. 1.1浅谈Spring(一个叫春的框架)

    如今各种Spring框架甚嚣尘上,但是终归还是属于spring的东西.所以在这里,个人谈一谈对spring的认识,笔者觉得掌握spring原理以及spring所涉及到的设计模式对我们具有极大的帮助.我 ...

  7. 浅谈ssh(struts,spring,hibernate三大框架)整合的意义及其精髓

    hibernate工作原理 原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Sesssion 4.创建事务Transation 5.持久化操作 6.提 ...

  8. [转] 浅谈ssh(struts,spring,hibernate三大框架)整合的意义及其精髓

      hibernate工作原理 原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Sesssion 4.创建事务Transation 5.持久化操作 6 ...

  9. Qt浅谈之十八:GraphicsView框架事件处理(有源码下载)

    一.简介 GraphicsView支持事件传播体系结构,可以使图元在场景scene中得到提高了已被的精确交互能力.图形视图框架中的事件都是首先由视图进行接收,然后传递给场景,再由场景给相应的图形项. ...

随机推荐

  1. C语言程序设计(基础)最后一次作业-- 总结报告

    本次作业是本学期的最后一次作业,有始有终,本次作业回顾下本学期的第0次作业,回答下面几个问题. 注意:在回答问题时请不要简单的回答 "不是","是".请把这当成 ...

  2. python 单向链表实现

    单链表的操作 is_empty() 链表是否为空 length() 链表长度 travel() 遍历整个链表 add(item) 链表头部添加元素 append(item) 链表尾部添加元素 inse ...

  3. Linux下vim上编辑实现进度条

    1.效果展示: 进度条,先来看一个效果: 这是进度结果,模拟实现了进度条的前进.百分比的现实.以及稍微的动画特效. 2.原理描述: 因为Linux系统下的输出有缓存,如果及时刷新显示,就可以得到我们想 ...

  4. 10-TypeScript中的接口

    接口是一种规约的约定,从接口继承的类必须实现接口的约定.在高级开发中,通常接口是用于实现各种设计模式的基础,没有接口,设计模式无从谈起. 定义接口: interface ILog{ recordlog ...

  5. LeetCode & Q1-Two Sum-Easy

    Array Hash Table Question Given an array of integers, return indices of the two numbers such that th ...

  6. Clover3(可以让Windows Explorer像浏览器一样有标签页)

    这不是广告!!! 下载地址:http://cn.ejie.me/ 效果图:

  7. mingw打dll ,lib包命令和调用

    1,下面的命令行将这个代码编译成 dll. gcc mydll.c -shared -o mydll.dll -Wl,--out-implib,mydll.lib 其中 -shared 告诉gcc d ...

  8. 帧动画的创建方式 - xml方式

    废话不多说,先看东西   创建帧动画1 - xml方式 帧动画的创建方式主要以下2种: * 用xml创建动画: * 用代码创建动画:   本文内容主要关注 xml文件 创建帧动画的方式   xml文件 ...

  9. Mego开发文档 - 数据库建模

    数据库建模 我们还提供了一些其他的特性,用于定制化数据库对应的数据结构. 表映射 框架默认会使用CLR类型名称做为实际数据库的表名,当两者不一致时可以使用该特性强制表名称. [Table(" ...

  10. 新概念英语(1-57)An unusual day

    新概念英语(1-57)An unusually day What is Mr. Sawyer doing tonight? It is eight o'clock. The children go t ...