Unit Of Work(UoW)模式在企业应用架构中被广泛使用,它能够将Domain Model中对象状态的变化收集起来,并在适当的时候在同一数据库连接和事务处理上下文中一次性将对象的变更提交到数据中。

从字面上我们可以我们可以把UnitOfWork叫做工作单元,从概念上它是协助代码块的事务。为什么我们需要用UnitOfWork?有人说EF不是的DbContext的SaveChanges不就有提交变更数据的功能吗?为什么还要多一层封装?是的,如果我们项目只是用EF的话,项目又会经常变更,不用考虑那么多我们可以直接用EF,但是如果我们在支持EF的同时还需要支持Redis、NHibernate或MongoDB呢?我们怎么做统一的事务管理?所以封装一个UnitOfWork是有必要的。类似的Repository也是一样,仓储Repository的功能其实就是EF的DbSet<T>,但是我们的数据库访问技术可能会改变,所以我们需要提供一层封装,来隔离领域层或应用层对数据访问层的依赖。那么ABP是怎么定义UnitOfWork的呢?

    public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle{/// <summary>/// Begins the unit of work with given options./// 开始 unit of work的一些配置UnitOfWorkOptions,主要是事务的级别,超时时间,配置文件等/// </summary>/// <param name="options">Unit of work options</param>void Begin(UnitOfWorkOptions options);}public interface IUnitOfWorkCompleteHandle : IDisposable{/// <summary>/// Completes this unit of work./// It saves all changes and commit transaction if exists./// 统一事务提交/// </summary>void Complete();/// <summary>/// Completes this unit of work./// It saves all changes and commit transaction if exists./// 异步的Complete方法/// </summary>Task CompleteAsync();}

从接口的定义来看,UnitOfWork主要涉及到事务的提交,回滚操作这边没有再定义一个方法,因为作者用的是TransactionScope,失败了会自动回滚。当然有定义了Dispose方法。现在我们来看下UnitOfWork的实现方法,抽象类UnitOfWorkBase,我删除了一些跟本文无关的代码,方便阅读。

    public abstract class UnitOfWorkBase : IUnitOfWork{public UnitOfWorkOptions Options { get; private set; }/// <summary>/// 开始UnitOfWork的一些配置,和事务的初始化/// </summary>/// <param name="options"></param>public void Begin(UnitOfWorkOptions options){if (options == null){throw new ArgumentNullException("options");}PreventMultipleBegin();Options = options; //TODO: Do not set options like that!SetFilters(options.FilterOverrides);BeginUow();}/// <summary>/// 事务的提交,异常的捕获/// </summary>public void Complete(){PreventMultipleComplete();try{CompleteUow();_succeed = true;OnCompleted();}catch (Exception ex){_exception = ex;throw;}}/// <summary>/// 结束事务,失败就回滚/// </summary>public void Dispose(){if (IsDisposed){return;}IsDisposed = true;if (!_succeed){OnFailed(_exception);}DisposeUow();OnDisposed();}}

我们知道UnitOfWorkBase是抽象类,对于不同的数据访问技术方案我们要定义不用的工作单元实现类,比如EF和NHibernate的事务实现机制是不一样的,这里我们看下EfUnitOfWork

    public class EfUnitOfWork : UnitOfWorkBase, ITransientDependency{private readonly IDictionary<Type, DbContext> _activeDbContexts;private readonly IIocResolver _iocResolver;private TransactionScope _transaction;/// <summary>/// Creates a new <see cref="EfUnitOfWork"/>./// </summary>public EfUnitOfWork(IIocResolver iocResolver, IUnitOfWorkDefaultOptions defaultOptions): base(defaultOptions){_iocResolver = iocResolver;_activeDbContexts = new Dictionary<Type, DbContext>();}protected override void BeginUow(){if (Options.IsTransactional == true){var transactionOptions = new TransactionOptions{IsolationLevel = Options.IsolationLevel.GetValueOrDefault(IsolationLevel.ReadUncommitted),};if (Options.Timeout.HasValue){transactionOptions.Timeout = Options.Timeout.Value;}_transaction = new TransactionScope(TransactionScopeOption.Required,transactionOptions,Options.AsyncFlowOption.GetValueOrDefault(TransactionScopeAsyncFlowOption.Enabled));}}public override void SaveChanges(){_activeDbContexts.Values.ForEach(SaveChangesInDbContext);}
...

上面已经定义了UnitOfWork接口和实现方法,那我们改怎么使用呢?一般的我们的使用方式是这样的,下面的场景是模拟银行转账功能,从一个账户扣钱和另一个账户加钱。下面是领域层定义的账户转账服务,我们在整个操作实现完后调用 _unitOfWork.Commit()进行提交,在领域服务构造函数注入UnitOfWork。

    // 账号转账领域服务类public class AccountService{private readonly IAccountRepository _productRepository;private readonly IUnitOfWork _unitOfWork;public AccountService(IAccountRepository productRepository, IUnitOfWork unitOfWork){_productRepository = productRepository;_unitOfWork = unitOfWork;            }public void Transfer(Account from, Account to, decimal amount){if (from.Balance >= amount){from.Balance -= amount;to.Balance += amount;_productRepository.Save(from);_productRepository.Save(to);_unitOfWork.Commit();}} }

这样的设计简单易懂,但是我们每个提交都要引用UnitOfWork会比较麻烦,那么有没有更好的设计思路呢?ABP的设计思想还是比较值得借鉴的。ABP的UnitOfWork的设计思路还是沿用作者最喜欢的切面编程,何为切面编程:通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。也就是AOP技术,ABP作者用的是Castle Windsor来实现的。一般的我们需要两步,1、继承IInterceptor接口重写Intercept方法,这样我们就可以实现动态拦截方法了,2、那么我们到底怎么才能动态代理要拦截的方法呢?我们可以继承Attribute,自定义UnitOfWorkAttribute。可能你现在还不明白,那么我们来看下具体代码吧。

    internal class UnitOfWorkInterceptor : IInterceptor{private readonly IUnitOfWorkManager _unitOfWorkManager;public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager){_unitOfWorkManager = unitOfWorkManager;}public void Intercept(IInvocation invocation){if (_unitOfWorkManager.Current != null){//Continue with current uowinvocation.Proceed();return;}var unitOfWorkAttr = UnitOfWorkAttribute.GetUnitOfWorkAttributeOrNull(invocation.MethodInvocationTarget);if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled){//No need to a uowinvocation.Proceed();return;}//No current uow, run a new onePerformUow(invocation, unitOfWorkAttr.CreateOptions());}

对于Castle Windsor我们只需要像上面的UnitOfWorkInterceptor就是继承IInterceptor重写Intercept就可以实现动态代理啦。下面来看下自定义的UnitOfWorkAttribute。

    [AttributeUsage(AttributeTargets.Method)]public class UnitOfWorkAttribute : Attribute{/// <summary>/// Is this UOW transactional?/// Uses default value if not supplied./// </summary>public bool? IsTransactional { get; private set; }/// <summary>/// Timeout of UOW As milliseconds./// Uses default value if not supplied./// </summary>public TimeSpan? Timeout { get; private set; }

好了,定义了UnitOfWorkAttribute,那么我们怎么让它和UnitOfWorkInterceptor结合起来对代码进行动态拦截呢?

        [UnitOfWork]public virtual async Task<AbpLoginResult> LoginAsync(string userNameOrEmailAddress, string plainPassword, string tenancyName = null){if (userNameOrEmailAddress.IsNullOrEmpty()){throw new ArgumentNullException("userNameOrEmailAddress");}if (plainPassword.IsNullOrEmpty()){throw new ArgumentNullException("plainPassword");}using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant)){TUser user;if (!_multiTenancyConfig.IsEnabled){using (_unitOfWorkManager.Current.EnableFilter(AbpDataFilters.MayHaveTenant)){//Log in with default denantuser = await FindByNameOrEmailAsync(userNameOrEmailAddress);if (user == null){return new AbpLoginResult(AbpLoginResultType.InvalidUserNameOrEmailAddress);}}}

上面代码是利用Attribute的特性对方法进行标识,这是第一步,现在我们已经对要拦截的代码标识了,那么我们是怎么知道它要被拦截的呢?

    internal static class UnitOfWorkRegistrar{/// <summary>/// Initializes the registerer./// </summary>sssss/// <param name="iocManager">IOC manager</param>public static void Initialize(IIocManager iocManager){iocManager.IocContainer.Kernel.ComponentRegistered += ComponentRegistered;}/// <summary>/// 拦截注册事件 /// </summary>/// <param name="key"></param>/// <param name="handler"></param>private static void ComponentRegistered(string key, IHandler handler){if (UnitOfWorkHelper.IsConventionalUowClass(handler.ComponentModel.Implementation)){//判断如果是IRepository和IApplicationService,就注册动态代理 Intercept all methods of all repositories.handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));}else if (handler.ComponentModel.Implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(UnitOfWorkHelper.HasUnitOfWorkAttribute)){//判断如果是被标识了UnitOfWork attribute的就注册动态代理 Intercept all methods of classes those have at least one method that has UnitOfWork attribute.//TODO: Intecept only UnitOfWork methods, not other methods!handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));}}}

请认真看上面的方法ComponentRegistered的代码,UnitOfWorkHelper.HasUnitOfWorkAttribute就是判断是否是UnitOfWorkAttribute。

        public static bool HasUnitOfWorkAttribute(MemberInfo methodInfo){return methodInfo.IsDefined(typeof(UnitOfWorkAttribute), true);}

你可能会问UnitOfWorkRegistrar的ComponentRegistered方法是什么时候执行的?那么你可以参考下我之前写的Castle Windsor常用介绍以及其在ABP项目的应用介绍 ,关于UnitOfWorkAttribute 是怎么执行的可以参考ABP之模块分析

那么到此我们被标识[UnitOfWork]的登录方法LoginAsync和所有的repositories仓储一旦被执行到就会被拦截,执行我们的代理类。本来这篇文章还想说说仓储的,但是因为篇幅可能会有点长,那就放在下次总结吧,至此UnitOfWork也就总结到此了。

参考文章:

http://www.cnblogs.com/daxnet/archive/2011/06/03/2071931.html

http://www.cnblogs.com/zhili/p/UnitOfWork.html

http://www.cnblogs.com/xishuai/p/3750154.html

转载于:https://www.cnblogs.com/huaizuo/p/4838680.html

UnitOfWork以及其在ABP中的应用相关推荐

  1. ABP中的Filter(下)

    接着上面的一个部分来叙述,这一篇我们来重点看ABP中的AbpUowActionFilter.AbpExceptionFilter.AbpResultFilter这三个部分也是按照之前的思路来一个个介绍 ...

  2. 【转】ABP源码分析四十七:ABP中的异常处理

    ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationExce ...

  3. 【转】ABP源码分析三十五:ABP中动态WebAPI原理解析

    动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...

  4. 【转】ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数必然已完成了执行 ...

  5. abp+dapper+mysql_ABP公共结构 – 时间与时区设置 - ABP 中文文档

    2.6 ABP公共结构 – 时间与时区设置 2.6.1 简介 虽然有些应用的需求是单时区,然而另一些是需要对不同的时区进行处理的.为了满足这样的需求以及对时间的处理.ABP提供了处理时间操作的通用基础 ...

  6. ABP源码分析四十七:ABP中的异常处理

    ABP源码分析四十七:ABP中的异常处理 参考文章: (1)ABP源码分析四十七:ABP中的异常处理 (2)https://www.cnblogs.com/1zhk/p/5538983.html (3 ...

  7. 记载abp中Dbcontext的疑问

    q:abp中httpcontext如何在一次请求中保证获取的是相同的实例. 大牛的原话: LifestylePerWebRequest does not works good with async. ...

  8. ABP中使用Redis Cache(1)

    本文将讲解如何在ABP中使用Redis Cache以及使用过程中遇到的各种问题.下面就直接讲解使用步骤,Redis环境的搭建请直接网上搜索. 使用步骤: 一.ABP环境搭建 到http://www.a ...

  9. ABP架构学习系列二:ABP中配置的注册和初始化

    一.手工搭建平台 1.创建项目 创建MVC5项目,手动引入Abp.Abp.Web.Abp.Web.Mvc.Abp.Web.Api 使用nuget添加Newtonsoft.Json.Castle.Cor ...

最新文章

  1. 20145221 《信息安全系统设计基础》第3周学习总结
  2. 在哪能找到陌生人聊骚_如何说服陌生人帮助您找到工作
  3. 前端学习(570):margin负值下的等高布局
  4. COM域名难逃实名监管 CN域名简化流程抢用户
  5. caffe学习笔记(2)
  6. 总结30个CSS3选择器(转载)
  7. singleton pattern的推荐实现
  8. 机器学习项目实战----泰坦尼克号获救预测(一)
  9. (原创)sqlite封装库SmartDB1.3发布
  10. SocksCap64全局代理工具使用+Clash使用命令行
  11. win10快捷键及浏览器快捷键
  12. BT5的 U盘启动 制作
  13. 在Qt下使用映美精黑白相机:Qt 5.12 + ImagingSource(映美精)+ vs2017 Community + OpenCV 3.3
  14. 鸡汤来了,成为数据分析师的快速指南
  15. 离线数据开发之任务调度系统
  16. Layer Emitter(图层发射器)
  17. mcldownload文件夹_download文件夹是什么?Win7系统download文件夹可以删除?
  18. 利用Hbuilder + Android Studio 制作安卓APP
  19. Vue this.$router.go(0) 刷新当前页面在苹果手机无效
  20. DC-DC电源原理电压反馈调节过压保护使能通俗讲解

热门文章

  1. 使用ADO.NET 的最佳实践(zz)
  2. DataTable中数据记录的统计
  3. 实现无刷新DropDownList联动效果
  4. ASP.NET 如何操作文件
  5. Spring源码分析【9】-SpringSecurity密码Remove原理
  6. 以金山界面库(openkui)为例思考和分析界面库的设计和实现——代码结构(完)
  7. 【Cmake】执行cmake命令时报错:No XSLT processor found
  8. 【Go】Go基础(八):结构体和方法
  9. bartender外部表不是预期格式_批量合并Excel数据时“外部表不是预期格式”或“文件包含损坏数据”的两种情况...
  10. windows游戏编程_少儿编程该怎么学?看看帕拉卡3D动画编程创始人李西峙分享七个核心...