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

其整体流程大概如下:

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

UnitOfWorkInterceptors

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

  1. private void AddInterceptorRegistrars()
  2. {
  3. ValidationInterceptorRegistrar.Initialize(IocManager);
  4. AuditingInterceptorRegistrar.Initialize(IocManager);
  5. EntityHistoryInterceptorRegistrar.Initialize(IocManager);
  6. UnitOfWorkRegistrar.Initialize(IocManager);
  7. AuthorizationInterceptorRegistrar.Initialize(IocManager);
  8. }

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

  1. public static void Initialize(IIocManager iocManager)
  2. {
  3. iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
  4. {
  5. var implementationType = handler.ComponentModel.Implementation.GetTypeInfo();
  6. HandleTypesWithUnitOfWorkAttribute(implementationType, handler);
  7. HandleConventionalUnitOfWorkTypes(iocManager, implementationType, handler);
  8. };
  9. }

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

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

  1. var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
  2. if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
  3. {
  4. //No need to a uow
  5. invocation.Proceed();
  6. return;
  7. }
  8. PerformUow(invocation, unitOfWorkAttr.CreateOptions());

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

如果是同步方法的话:

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

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

  1. private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
  2. {
  3. // 获得一个工作单元
  4. var uow = _unitOfWorkManager.Begin(options);
  5. // 继续执行拦截到的方法
  6. try
  7. {
  8. invocation.Proceed();
  9. }
  10. catch
  11. {
  12. uow.Dispose();
  13. throw;
  14. }
  15. // 如果是异步无返回值的方法
  16. if (invocation.Method.ReturnType == typeof(Task))
  17. {
  18. invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
  19. (Task) invocation.ReturnValue,
  20. async () => await uow.CompleteAsync(),
  21. exception => uow.Dispose()
  22. );
  23. }
  24. else //Task<TResult>
  25. {
  26. invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
  27. invocation.Method.ReturnType.GenericTypeArguments[0],
  28. invocation.ReturnValue,
  29. async () => await uow.CompleteAsync(),
  30. exception => uow.Dispose()
  31. );
  32. }
  33. }

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

  1. public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
  2. {
  3. Exception exception = null;
  4. try
  5. {
  6. // 原有异步任务返回值
  7. await actualReturnValue;
  8. // 新的异步返回结果
  9. await postAction();
  10. }
  11. catch (Exception ex)
  12. {
  13. exception = ex;
  14. throw;
  15. }
  16. finally
  17. {
  18. finalAction(exception);
  19. }
  20. }

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

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

  1. public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction)
  2. {
  3. // 获得 AwaitTaskWithPreActionAndPostActionAndFinallyAndGetResult 方法,并且通过反射构造一个泛型方法,并且将自身参数传入调用。
  4. return typeof (InternalAsyncHelper)
  5. .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
  6. .MakeGenericMethod(taskReturnType)
  7. .Invoke(null, new object[] { actualReturnValue, action, finalAction });
  8. }
  9. public static async Task<T> AwaitTaskWithPreActionAndPostActionAndFinallyAndGetResult<T>(Func<Task<T>> actualReturnValue, Func<Task> preAction = null, Func<Task> postAction = null, Action<Exception> finalAction = null)
  10. {
  11. Exception exception = null;
  12. try
  13. {
  14. if (preAction != null)
  15. {
  16. await preAction();
  17. }
  18. var result = await actualReturnValue();
  19. if (postAction != null)
  20. {
  21. await postAction();
  22. }
  23. return result;
  24. }
  25. catch (Exception ex)
  26. {
  27. exception = ex;
  28. throw;
  29. }
  30. finally
  31. {
  32. if (finalAction != null)
  33. {
  34. finalAction(exception);
  35. }
  36. }
  37. }

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

IUnitOfWorkManager

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

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

  1. public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
  2. {
  3. // 如果没有 UOW 参数,构造默认参数
  4. options.FillDefaultsForNonProvidedOptions(_defaultOptions);
  5. var outerUow = _currentUnitOfWorkProvider.Current;
  6. // 当前是否已经存在工作单元,存在工作单元的话,构建一个内部工作单元
  7. if (options.Scope == TransactionScopeOption.Required && outerUow != null)
  8. {
  9. return new InnerUnitOfWorkCompleteHandle();
  10. }
  11. // 不存在的话构建一个新的工作单元
  12. var uow = _iocResolver.Resolve<IUnitOfWork>();
  13. // 绑定各种事件
  14. uow.Completed += (sender, args) =>
  15. {
  16. _currentUnitOfWorkProvider.Current = null;
  17. };
  18. uow.Failed += (sender, args) =>
  19. {
  20. _currentUnitOfWorkProvider.Current = null;
  21. };
  22. uow.Disposed += (sender, args) =>
  23. {
  24. _iocResolver.Release(uow);
  25. };
  26. //Inherit filters from outer UOW
  27. if (outerUow != null)
  28. {
  29. options.FillOuterUowFiltersForNonProvidedOptions(outerUow.Filters.ToList());
  30. }
  31. uow.Begin(options);
  32. //Inherit tenant from outer UOW
  33. if (outerUow != null)
  34. {
  35. uow.SetTenantId(outerUow.GetTenantId(), false);
  36. }
  37. // 设置当前工作单元为新创建的 UOW
  38. _currentUnitOfWorkProvider.Current = uow;
  39. return uow;
  40. }

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

  1. public interface IUnitOfWorkCompleteHandle : IDisposable
  2. {
  3. void Complete();
  4. Task CompleteAsync();
  5. }

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

InnerUnitOfWorkCompleteHandle

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

  1. internal class InnerUnitOfWorkCompleteHandle : IUnitOfWorkCompleteHandle
  2. {
  3. public const string DidNotCallCompleteMethodExceptionMessage = "Did not call Complete method of a unit of work.";
  4. private volatile bool _isCompleteCalled;
  5. private volatile bool _isDisposed;
  6. public void Complete()
  7. {
  8. _isCompleteCalled = true;
  9. }
  10. public Task CompleteAsync()
  11. {
  12. _isCompleteCalled = true;
  13. return Task.FromResult(0);
  14. }
  15. public void Dispose()
  16. {
  17. if (_isDisposed)
  18. {
  19. return;
  20. }
  21. _isDisposed = true;
  22. if (!_isCompleteCalled)
  23. {
  24. if (HasException())
  25. {
  26. return;
  27. }
  28. throw new AbpException(DidNotCallCompleteMethodExceptionMessage);
  29. }
  30. }
  31. private static bool HasException()
  32. {
  33. try
  34. {
  35. return Marshal.GetExceptionCode() != 0;
  36. }
  37. catch (Exception)
  38. {
  39. return false;
  40. }
  41. }
  42. }

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

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

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

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

你可以想象有以下代码:

  1. public void TestUowMethod()
  2. {
  3. using(var outerUOW = Manager.Begin()) // 这里返回的是 IOC 解析出的 IUnitOfWork
  4. {
  5. OperationOuter();
  6. using(var innerUOW1 = Manager.Begin()) // 内部 UOW
  7. {
  8. Operation1();
  9. using(var innerUOW2 = Manager.Begin()) // 内部 UOW
  10. {
  11. Operation2();
  12. Complete();
  13. }
  14. Complete();
  15. }
  16. Complete();
  17. }
  18. }

当代码执行的时候,如同俄罗斯套娃,从内部依次到外部执行,内部工作单元仅会在调用 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 属性上面。形成如下结构:

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

具体代码参考 ICurrentUnitOfWorkProvider 的实现。

Begin()

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

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

BeginUow()-EfCoreUnitOfWork

  1. protected override void BeginUow()
  2. {
  3. if (Options.IsTransactional == true)
  4. {
  5. _transactionStrategy.InitOptions(Options);
  6. }
  7. }

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

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

  1. using(var a = Manager.Begin())
  2. {
  3. // 操作
  4. using(var b = Manager.Begin())
  5. {
  6. // 操作
  7. b.Complete();
  8. }
  9. a.Complete();
  10. }

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

可以看下 EfUnitOfWork 的实现:

  1. // 这里是 UnitOfWorkBase 的 Complete 方法
  2. public void Complete()
  3. {
  4. PreventMultipleComplete();
  5. try
  6. {
  7. CompleteUow();
  8. _succeed = true;
  9. OnCompleted();
  10. }
  11. catch (Exception ex)
  12. {
  13. _exception = ex;
  14. throw;
  15. }
  16. }
  1. // 这里是 EfUnitOfWork 的 CompleteUow 方法
  2. protected override void CompleteUow()
  3. {
  4. SaveChanges();
  5. CommitTransaction();
  6. }
  7. // 遍历所有激活的 DbContext 并保存更改到数据库
  8. public override void SaveChanges()
  9. {
  10. foreach (var dbContext in GetAllActiveDbContexts())
  11. {
  12. SaveChangesInDbContext(dbContext);
  13. }
  14. }
  15. // 提交事务
  16. private void CommitTransaction()
  17. {
  18. if (Options.IsTransactional == true)
  19. {
  20. _transactionStrategy.Commit();
  21. }
  22. }

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

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

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

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

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

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

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

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

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

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

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

  6. ABP框架 - 工作单元

    文档目录 本节内容: 简介 在ABP中管理连接和事务 约定的工作单元 UnitOfWork 特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 非事务性工作单元 工作单元方法调用另 ...

  7. 手工搭建基于ABP的框架 - 工作单元以及事务管理

    一个业务功能往往不只由一次数据库请求(或者服务调用)实现.为了功能的完整性,我们希望如果该功能执行一半时出错,则撤销前面已执行的改动.在数据库层面上,事务管理实现了这种完整性需求.在ABP中,一个完整 ...

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

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

  9. 浅谈模块系统与 ABP 框架初始化

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

随机推荐

  1. N2N windows下编译安装文件

    n2n安装 n2n原理编译版下载,可直接使用:windows下vpn客户端 n2n_v2_linux_x64 n2n_v2_Win32TAP网卡驱动 #linux环境编译yum install -y ...

  2. 关于jquery的选择器中的空格问题

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. Chapter6_访问权限控制_类的访问权限

    在Java中,访问权限修饰词也可以用于确定库中的哪些类对于该库的使用者是可用的,类既不可以是private也不可以是protected的,对于类的访问权限,只有两种选择:包访问权限或者public.下 ...

  4. U3D一些使用

    1,UGUI Button同时监听点击事件和长按事件:https://blog.csdn.net/yye4520/article/details/80844870

  5. 使用 mybatis plus 动态数据源

    1.pom.xml 增加 <dependency> <groupId>com.baomidou</groupId> <artifactId>dynami ...

  6. js浮点数加减乘除精度不准确

    做个记录,以备不时之需 //加法 Number.prototype.add = function(arg){ var r1,r2,m; try{r1=this.toString().split(&qu ...

  7. 阿里云服务器搭建SS代理教程!!!

    二.搭建教程 1.环境介绍 阿里云服务器ECS(香港): 配置:cpu 1核心.内存 1GB.出网带宽 10Mbps. 系统:CentOS 7.4 64位 2.服务器端搭建 1)使用root用户,分别 ...

  8. ionic 侧栏菜单用法

    第一步: 引入js和css文件我这里是直接引入的cdn,ionic是基于angular的,bundle.min.js把常用angular的js已经压缩到一起,可以直接引入.bundle.min.js, ...

  9. Numpy1

    列表转n维数组ndarray import numpy as np list=[1,2,3,4] n=np.array(list) random模块生成ndarray n1=np.random.ran ...

  10. HDU - 1241 Oil Deposits 经典dfs 格子

    最水的一道石油竟然改了一个小时,好菜好菜. x<=r  y<=c  x<=r  y<=c  x<=r  y<=c  x<=r y<=c #include ...