ABP 數據訪問 - UnitOfWork 工作單元


ABP 工作單元

ABP中的工作單元是基於AOP實現;采用 Castle組件來完成攔截;
Castle.DynamicProxy
:使用Castle的動態代理來完成方法的攔截

我們首先還是來分析下ABP中工作單元的整個結構圖;

還是先上整體的結構圖

只是描述了類的關聯關系,很多成員並不准確 😄

1.Castle.DynamicProxy 攔截器(UOW注入)

一步步來接開工作單元的面紗;

ABP使用Castle的動態代理來攔截方法,進行AOP注入,再方法之前開啟事務,在方法執行之后統一提交事務;

** Castle.DynamicProxy.IAsyncInterceptor**
該接口定義了標准的攔截器動作,ABP當然也實現了具體的操作

** Abp.Dependency.AbpInterceptorBase**
繼承 IAsyncInterceptor
ABP的基礎連接器,定義了通用的攔截方法,例如異步攔截,同步攔截,以及攔截方法的返回值處理等;

 public virtual void InterceptAsynchronous(IInvocation invocation)
        {
            invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
        }
        public virtual void InterceptAsynchronous<TResult>(IInvocation invocation)
        {
            invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);
        }
        public abstract void InterceptSynchronous(IInvocation invocation);
        protected abstract Task InternalInterceptAsynchronous(IInvocation invocation);
        protected abstract Task<TResult> 
InternalInterceptAsynchronous<TResult>(IInvocation invocation);


** UnitOfWorkInterceptor**
繼承 AbpInterceptorBase
定義工作單元的攔截方法,定義在方法執行前以及執行后的相關操作,這里的設計我們可以參考到其他操作中,比如日志處理,我們也可以參考這種設計方法來實現;

話不多說,來分析下具體的攔截過程

protected override async Task InternalInterceptAsynchronous(IInvocation invocation)
        {
            var proceedInfo = invocation.CaptureProceedInfo();
            var method = GetMethodInfo(invocation);
            var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
            if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
            {
               proceedInfo.Invoke();
                var task = (Task)invocation.ReturnValue;
                await task.ConfigureAwait(false);
                return;
            }
            using (var uow = _unitOfWorkManager.Begin(unitOfWorkAttr.CreateOptions()))
            {
                proceedInfo.Invoke();
                var task = (Task)invocation.ReturnValue;
                await task.ConfigureAwait(false);
                await uow.CompleteAsync().ConfigureAwait(false);
            }
        }

1.做開啟工作單元的判斷
var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);

獲取方法屬性,如果標記了UnitOfWork,則開啟工作單元攔截

2.開啟工作單元

using (var uow = _unitOfWorkManager.Begin(unitOfWorkAttr.CreateOptions()))
            {
                proceedInfo.Invoke();
                var task = (Task)invocation.ReturnValue;
                await task.ConfigureAwait(false);
                await uow.CompleteAsync().ConfigureAwait(false);
            }

這里開啟工作單元,通過unitWorkManager.Begin來開啟,工作單元的提交通過 IUnitOfWorkCompleteHandle 實現;

到此,我們對 UOW 的注入方式已經了解,知道是如何注入到方法中,並進行攔截的。

我們帶着問題進入下一步
unitWorkManager如何控制的事務?
如何進行的事務提交?
以及方法嵌套調用時,如何確保同一事務提交?

2.IUnitOfWork 工作單元實現

IUnitOfWorkCompleteHandle : 定義了UOW同步和異步的complete方法。實現UOW完成時候的邏輯。
IActiveUnitOfWork :一個UOW除了以上兩個接口中定義的方法和屬性外,其他的屬性和方法都在這個接口定義的。比如Completed,Disposed,Failed事件代理,Filter的enable和disable,以及同步、異步的SaveChanges方法。
IUnitOfWork :繼承了上面兩個接口。定義了外層的IUnitOfWork的引用和UOW的begin方法

三個接口來一起完成 UOW 工作;

UnitOfWorkBase : 作為實現上面三個接口的基礎實現,實現一些基礎方法,但是真正的事務操作實現都是各個框架具體的實現,例如:EFUnitOfWork,EfCoreUnitOfWork等不同的實現;

好,接着來,把整體大的結構講完,接着看代碼,上面注入提到會調用UnitOfManager來管理,那我們從它入手,首先分析Begin方法,看下Manager的實現

public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
        {           
         options.FillDefaultsForNonProvidedOptions(_defaultOptions);
            var outerUow = _currentUnitOfWorkProvider.Current;
            if (options.Scope == TransactionScopeOption.Required && outerUow != null)
            {
                return new InnerUnitOfWorkCompleteHandle();
            }
            var uow = _iocResolver.Resolve<IUnitOfWork>();
            uow.Completed += (sender, args) =>
            {
                _currentUnitOfWorkProvider.Current = null;
            };
            uow.Failed += (sender, args) =>
            {
               _currentUnitOfWorkProvider.Current = null;
            };
            uow.Disposed += (sender, args) =>
            {
                _iocResolver.Release(uow);
            };
            //Inherit filters from outer UOW
            if (outerUow != null)
            {
  options.FillOuterUowFiltersForNonProvidedOptions(outerUow.Filters.ToList());
            }
            uow.Begin(options);
            //Inherit tenant from outer UOW
            if (outerUow != null)
            {
                uow.SetTenantId(outerUow.GetTenantId(), false);
            }
            _currentUnitOfWorkProvider.Current = uow;
            return uow;
        }

1.獲取當前環境事務單元

            var outerUow = _currentUnitOfWorkProvider.Current;
            if (options.Scope == TransactionScopeOption.Required && outerUow != null)
            {
                return new InnerUnitOfWorkCompleteHandle();
            }

獲取當前環境中的事務單元,如果不為空說明是嵌套方法調用,這里通過_currentUnitOfWorkProvider來管理,如果是嵌套方法中的工作單元,則返回一個內部提交操作類,InnerUnitOfWorkCompleteHandle 該類不處理具體事務操作,它的提交只是標識下方法執行狀態

進入Begin方法,如果是第一個方法進來,獲取Current為空,就會重新創建一個Uow,如果是嵌套的方法調用,那會直接返回一個InnerUnitOfWorkCompleteHandle,這里的結構設計還是比較巧妙,解決了工作單元方法嵌套調用,保證存在於一個事務中

public void Complete()
        {
            _isCompleteCalled = true;
        }

2.綁定相關事件,並開啟事務

Begin方法,首先會調用到 UnitOfWorkBase 的Begin方法

public void Begin(UnitOfWorkOptions options)
        {
            Check.NotNull(options, nameof(options));
            PreventMultipleBegin();
            Options = options; //TODO: Do not set options like that, instead make a 
copy?
            SetFilters(options.FilterOverrides);
            SetTenantId(AbpSession.TenantId, false);
            BeginUow();
        }

然后調用BeginUow(),Base類中並沒有具體實現,這里會調用不同實現,這里分析下EfCore實現;

EfCoreUnitOfWork

protected override void BeginUow()
        {
            if (Options.IsTransactional == true)
            {
                _transactionStrategy.InitOptions(Options);
            }
        }

這里會將事務參數傳遞進來,作為EFCore的事務控制,那后續如何和事務關聯呢?如果傳遞的參數是開啟事務模式,后續創建DbContext會以事務模式
這里訪問到 DbContextEfCoreTransactionStrategy

 public DbContext CreateDbContext<TDbContext>(string connectionString, IDbContextResolver 
dbContextResolver) where TDbContext : DbContext

        {
            DbContext dbContext;
            var activeTransaction = ActiveTransactions.GetOrDefault(connectionString);
            if (activeTransaction == null)
            {
                dbContext = dbContextResolver.Resolve<TDbContext>(connectionString, null);
                var dbtransaction = 
dbContext.Database.BeginTransaction((Options.IsolationLevel ?? 
IsolationLevel.ReadUncommitted).ToSystemDataIsolationLevel());
                activeTransaction = new ActiveTransactionInfo(dbtransaction, dbContext);
                ActiveTransactions[connectionString] = activeTransaction;

            }
            else
            {
                dbContext = dbContextResolver.Resolve<TDbContext>(
                    connectionString,          activeTransaction.DbContextTransaction.GetDbTransaction().Connection
                );
                if (dbContext.HasRelationalTransactionManager())
                {
dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction());
                }
                else
                {
                    dbContext.Database.BeginTransaction();
                }
              activeTransaction.AttendedDbContexts.Add(dbContext);

            }
            return dbContext;
        }

關注這行代碼
var dbtransaction = dbContext.Database.BeginTransaction((Options.IsolationLevel ?? IsolationLevel.ReadUncommitted).ToSystemDataIsolationLevel());
根據傳入的參數,進行事務的創建

3.工作單元提交
工作單元在完成操作之后,如何進行同一的事務處理呢?還是來分析下EFCore實現
EfCoreUnitOfWork

protected override void CompleteUow()

        {
            SaveChanges();
            CommitTransaction();
        }

循環找到所有DbContext,進行保存操作

public override void SaveChanges()
        {
            foreach (var dbContext in GetAllActiveDbContexts())
            {
                SaveChangesInDbContext(dbContext);
            }
        }
private void CommitTransaction()
        {
            if (Options.IsTransactional == true)
            {
                _transactionStrategy.Commit();
            }
        }

調用 DbContextEfCoreTransactionStrategy,進行事務的統一提交

public void Commit()
        {
            foreach (var activeTransaction in ActiveTransactions.Values)
            {
                activeTransaction.DbContextTransaction.Commit();
                foreach (var dbContext in activeTransaction.AttendedDbContexts)
                {
                    if (dbContext.HasRelationalTransactionManager())
                    {
                        continue; //Relational databases use the shared transaction
                    }
                    dbContext.Database.CommitTransaction();
                }
            }
        }

至此,UnitOfWork的基本實現就已經分析完了,簡單的對代碼結構進行了整體解讀,但是還是沒有對包含的所有類全部分析完成,但是整體的思路結構都已經分析到了,后面的ABP系列也是會繼續,歡迎大家來交流;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM