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. 为VS2013添加SQLCE的支持

    解决 下载SQL Server Compact Toolbox by ErikEJ并安装 打开VS2013,新建一工程,在“视图>其它窗口>SQL Server Compact Toolb ...

  2. [Network]Application Layer

    1 Principles of Network Applications 1.1 Application Architectures Client-Server Peer-to-Peer Hybird ...

  3. .NET/C# RabbitMQ

    本系列文章均来自官网原文,属于个人翻译,如有雷同,权当个人归档,忽喷. RabitMQ 是一个消息中间件,其实就是从消息生产者那里接受消息,然后发送给消息消费者.在这个传输过程中,可以定义一些缓存,持 ...

  4. oracle转mysql总结(转)

    ares-sdk初始开发测试使用的是oracle数据库,由于宁波通商的特殊需要,必须把数据库环境从oracle转向mysql. 现对转换过程中出现的问题及经验总结如下: 主键生成策略 创建一个专门记录 ...

  5. 三白话经典算法系列 Shell排序实现

    山是包插入的精髓排序排序,这种方法,也被称为窄增量排序.因为DL.Shell至1959提出命名. 该方法的基本思想是:先将整个待排元素序列切割成若干个子序列(由相隔某个"增量"的元 ...

  6. Docker镜像与容器命令(转)

    Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的.可移植的.自给自足的容器.开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机).bare metal. ...

  7. CSS: 解决Div float后,父Div无法高度自适应的问题

    在用CSS+DIV的布局中,常常会发现,当一个DIV float之后,假设他的高度超过了其父DIV的高度时,其父DIV的高度并不会对应的进行调整.要解决问题(也叫做闭合(清除)浮动),我们有四种办法: ...

  8. Byte[]和BASE64之间的转换

    一. BASE64编码 把byte[]中的元素当做无符号八位整数转换成只含有64个基本字符的字符串,这些基本字符是: l 大写的A-Z l 小写的a-z l 数字0-9 l '+' 和 '/' l 空 ...

  9. 怎么样cocos2d-x正在使用ECS(实体-包裹-制)建筑方法来开发一款游戏?

    简介 在我的博客,我翻译的几篇文章ECS文章.这些文章都是从Game Development站点.假设你对这个架构方式还不是非常了解的话.欢迎阅读理解 组件-实体-系统和实现 组件-实体-系统. 我发 ...

  10. 2.大约QT数据库操作,简单的数据库连接操作,增删改查数据库,QSqlTableModel和QTableView,事务性操作,大约QItemDelegate 代理

     Linux下的qt安装,命令时:sudoapt-get install qt-sdk 安装mysql数据库,安装方法參考博客:http://blog.csdn.net/tototuzuoquan ...