UnitOfWork以及其在ABP中的應用

Unit Of Work(UoW)模式在企業應用架構中被普遍使用,它可以將Domain Model中對象狀態的變化收集起來,並在適當的時候在同一數據庫鏈接和事務處理上下文中一次性將對象的變動提交到數據中。html

從字面上咱們能夠咱們能夠把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來實現的。通常的咱們須要兩步,一、繼承IInterceptor接口重寫Intercept方法,這樣咱們就能夠實現動態攔截方法了,二、那麼咱們到底怎麼才能動態代理要攔截的方法呢?咱們能夠繼承Attribute,自定義UnitOfWorkAttribute。可能你如今還不明白,那麼咱們來看下具體代碼吧。async

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

    [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的特性對方法進行標識,這是第一步,如今咱們已經對要攔截的代碼標識了,那麼咱們是怎麼知道它要被攔截的呢?post

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

        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

相關文章
相關標籤/搜索