UnitOfWork以及其在ABP中的应用

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的呢?

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
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,我删除了一些跟本文无关的代码,方便阅读。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public abstract class UnitOfWorkBase : IUnitOfWork
{
    public UnitOfWorkOptions Options { getprivate 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

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

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
// 账号转账领域服务类
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。可能你现在还不明白,那么我们来看下具体代码吧。

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
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[AttributeUsage(AttributeTargets.Method)]
public class UnitOfWorkAttribute : Attribute
{
    /// <summary>
    /// Is this UOW transactional?
    /// Uses default value if not supplied.
    /// </summary>
    public bool? IsTransactional { getprivate set; }
 
    /// <summary>
    /// Timeout of UOW As milliseconds.
    /// Uses default value if not supplied.
    /// </summary>
    public TimeSpan? Timeout { getprivate set; }

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

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
[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的特性对方法进行标识,这是第一步,现在我们已经对要拦截的代码标识了,那么我们是怎么知道它要被拦截的呢?

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
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。

1
2
3
4
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应用的更多相关文章

  1. 【无私分享:ASP.NET CORE 项目实战(第五章)】Repository仓储 UnitofWork

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 本章我们来创建仓储类Repository 并且引入 UnitOfWork 我对UnitOfWork的一些理解  UnitOfW ...

  2. UnitOfWork以及其在ABP中的应用

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

  3. MVC+UnitOfWork+Repository+EF 之我见

    UnitOfWork+Repository模式简介: 每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性.一致性.解决办法是:在Reposito ...

  4. 工作单元模式(UnitOfWork)学习总结

    工作单元的目标是维护变化的对象列表.使用IUnitOfWorkRepository负责对象的持久化,使用IUnitOfWork收集变化的对象,并将变化的对象放到各自的增删改列表中, 最后Commit, ...

  5. 开发笔记:用不用UnitOfWork以及Repository返回什么集合类型

    这2天实际开发中明确的东西,在这篇博文中记录一下. 之前对是否需要自己封装UnitOfWork有些犹豫,因为Entity Framework就是一个UnitOfWork实现, 自己再封装一下显得有些多 ...

  6. DDD:Repository和UnitOfWork的生命周期问题

    UnitOfWork UnitOfWork是一种有状态的.用例级别的对象.如果不采用ORM是不会使用UnitOfWork模式的, Repository Repository是一种特殊的领域服务,因此是 ...

  7. [Architect] Abp 框架原理解析(5) UnitOfWork

    本节目录 介绍 分析Abp源码 实现UOW 介绍 UOW(全称UnitOfWork)是指工作单元. 在Abp中,工作单元对于仓储和应用服务方法默认开启.并在一次请求中,共享同一个工作单元. 同时在Ab ...

  8. UnitOfWork机制的实现和注意事项

    UnitOfWork机制 /*一点牢骚: * UnitOfWork机制的蛋疼之处: *    UnitOfWork机制,决定了插入新的实体前,要预先设置数据库中的主键Id,尽管数据库自己生产主键. * ...

  9. Linq 与UnitOfWork

    submitchages(linq to sql)或者savechanges(ef)的次数是根据你操作方法的数量决定的,也即是:它只认识自己的提交语句(submtchanges,savechanges ...

  10. 关于 Repository和UnitOfWork 模式的关系

    本以为,关于这方面的理解,园子中的文章已经很多的了,再多做文章真的就“多做文章了”,但是最近发现,还是有必要的,首先,每个人对于同一事物的理解方式和出发点都是不同的,所以思考的方式得到结果也是不同的. ...

随机推荐

  1. hash在Coreseek 中配置bigint

    304 $sphinxapi->SetSelect('id, domain_hash'); 304 $sphinxapi->SetConnectTimeout(3); 305 $sphin ...

  2. J2EE的13个规范之JDBC

    假设让你接触一样新的东西.你可能感觉无所适从,可是假设本来就是旧事物的话,你学习起来还难吗? 一.ODBC,我们的老朋友 ODBC(Open Database Connectivity)是微软公司与数 ...

  3. const使用摘要

    const在四种方案如以下: int b = 500; const int *a = &b; ①(底层const) int const *a = &b; ②(底层const) int ...

  4. ACM:回溯,八皇后问题,素数环

    (一)八皇后问题 (1)回溯 #include <iostream> #include <string> #define MAXN 100 using namespace st ...

  5. django 带參数的 url

    url就像筋络一样把django这个大框架的各个部分紧紧的连接成一个总体,所以要了解django从url開始是一个不错的方向. 一般的view template url的关系这里就不讲了,以下会具体介 ...

  6. 重新想象 Windows 8 Store Apps (15) - 控件 UI: 字体继承, Style, ControlTemplate, SystemResource, VisualState, VisualStateManager

    原文:重新想象 Windows 8 Store Apps (15) - 控件 UI: 字体继承, Style, ControlTemplate, SystemResource, VisualState ...

  7. 20140719中国互联网公司市值排名TOP20

    近期在找工作.关注了一下中国互联网公司的市值,实际情况跟想象的区别非常大. 比方异军突起的小米.京东.唯品会.聚美优品. 比方乐视网由于政策原因,市值两日缩水10亿$.停牌了. 搜房网市值90天蒸发3 ...

  8. C++学习笔记9-运算符重载

    1. 重载运营商必须有一个类类型的操作数 对于内置类型运营商.它的意义不能改变. 例如,内置整数加法运算不能被重新定义: // error: cannotredefine built-in opera ...

  9. Net Memory Profiler 分析.Net程序内存泄露

    Net Memory Profiler 分析.Net程序内存泄露 Haozes's Tech Space 人類的全部才能無非是時間和耐心的混合物 使用.Net Memory Profiler 分析.N ...

  10. c# 通过配置自动附加数据库

    using System; using System.Collections.Generic; using System.Windows.Forms; using System.Data.SqlCli ...