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 uow
invocation.Proceed();
return;
} var unitOfWorkAttr = UnitOfWorkAttribute.GetUnitOfWorkAttributeOrNull(invocation.MethodInvocationTarget);
if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
{
//No need to a uow
invocation.Proceed();
return;
} //No current uow, run a new one
PerformUow(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 denant
user = 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

UnitOfWork以及其在ABP中的应用的更多相关文章

  1. ABP中的Filter(下)

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

  2. ABP中模块初始化过程(二)

    在上一篇介绍在StartUp类中的ConfigureService()中的AddAbp方法后我们再来重点说一说在Configure()方法中的UserAbp()方法,还是和前面的一样我们来通过代码来进 ...

  3. ABP源码分析二:ABP中配置的注册和初始化

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

  4. ABP源码分析三十五:ABP中动态WebAPI原理解析

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

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

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

  6. ABP中使用Redis Cache(1)

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

  7. ABP中使用Redis Cache(2)

    上一篇讲解了如何在ABP中使用Redis Cache,虽然能够正常的访问Redis,但是Redis里的信息无法同步更新.本文将讲解如何实现Redis Cache与实体同步更新.要实现数据的同步更新,我 ...

  8. ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)

    ABP目前的认证方式有两种,一种是基于Cookie的登录认证,一种是基于token的登录认证.使用Cookie的认证方式一般在PC端用得比较多,使用token的认证方式一般在移动端用得比较多.ABP自 ...

  9. 在Abp中集成Swagger UI功能

    在Abp中集成Swagger UI功能 1.安装Swashbuckle.Core包 通过NuGet将Swashbuckle.Core包安装到WebApi项目(或Web项目)中. 2.为WebApi方法 ...

随机推荐

  1. Spring学习记录(三)---bean自动装配autowire

    Spring IoC容器可以自动装配(autowire)相互协作bean之间的关联关系,少写几个ref autowire: no ---默认情况,不自动装配,通过ref手动引用 byName---根据 ...

  2. windows server 2008 r2 企业版 hyper v做虚拟化的相关问题处理

    windows server 2008 r2 企业版 hyper v做虚拟化的相关问题处理 今天在dell r710 上用windows server 2008 r2企业版hyper v 做虚拟化,添 ...

  3. C标准库-数值字符串转换与内存分配函数

    原文链接:http://www.orlion.ga/977/ 一.数值字符串转换函数 #include <stdlib.h> int atoi(const char *nptr); dou ...

  4. jQuery之empty、remove、detach

    三者都有把元素移除的作用,但细微的差别,造就了它们的使命不同. 最权威的解释当然是jQuery_API咯,下面是API中关于他三儿的部分截取. 一.empty: This method removes ...

  5. YII 的源码分析(三)

    前面已经看完了启动一个yii程序所要经过的流程,以及渲染一个页面是怎么完成的.今天要分析的是yii是如何处理用户请求的.也就是控制和动作部分. 还是以helloworld为例演示这一过程.我们在地址栏 ...

  6. bootstrap 学习总结

    Bootstrap 是最受欢迎的 HTML.CSS 和 JS 框架,用于开发响应式布局.移动设备优先的 WEB 项目.当前最新版本是3.2.0; 使用的第一步,可以直接复制官方的模版,另存为index ...

  7. 深入理解 OWIN 中的 Host 和 Server

    The Open Web Interface for .NET (OWIN),注意单词为大写,之前好像都写成了 Owin,但用于项目的时候,可以写成:Microsoft.Owin.*. OWIN 体系 ...

  8. 小菜学习Winform(二)WMPLib实现音乐播放器

    前言 现在网上有很多的音乐播放器,但好像都不是.net平台做的,在.net中实现音乐文件的播放功能很简单,下面就简单实现下. SoundPlayer类 在.net提供了音乐文件的类:SoundPlay ...

  9. 制作动画或小游戏——CreateJS基础类(一)

    前面曾经记录过Canvas的基础知识<让自己也能使用Canvas>,在实际使用中,用封装好的库效率会高点. 使用成熟的库还能对基础知识有更深入的理解,CreateJS是基于HTML5开发的 ...

  10. ZOJ Problem Set - 1201 Inversion

    题目:这道题目的意思让人猛地一读有点反应不过来,简单解释下: 给定序列A:a1,a2,a3....,an,如果i<j且ai>aj则(ai,aj)称为序列A的一个倒置. 之后引出了序列的倒置 ...