ABP是“ASP.NET Boilerplate Project”的簡稱,是基于DDD領域驅動理念設計的架構。在面對新知識的出現,我們不僅要學會使用,更要追根究底,本系列将結合使用和架構實作來對ABP進行全面解析。本篇将對ABP中Unit of Work的實作進行解析。
前言
ABP
ABP是“ASP.NET Boilerplate Project”的簡稱。
ABP的官方網站:http://www.aspnetboilerplate.com
ABP在Github上的開源項目:https://github.com/aspnetboilerplate
ABP其他學習部落格推薦及介紹:http://www.cnblogs.com/mienreal/p/4528470.html
ABP中Unit of Work概念及使用
如果這是你首次接觸ABP架構或ABP的Unit of Work,推薦先看看 ABP使用及架構解析系列-[Unit of Work part.1-概念及使用]
架構實作
溫馨提示
1.ABP的Unit of Work相關代碼路徑為:/Abp/Domain/Uow
2.架構實作中,代碼不會貼全部的,但是會說明代碼在項目中的位置,并且為了更加直覺和縮短篇幅,對代碼更細緻的注釋,直接在代碼中,不要忘記看代碼注釋哈,。
3.部落格中的代碼,有一些方法是可以點選的!
動态代理/攔截器/AOP
上面講到Unit of Work有兩個預設實作,領域服務和倉儲庫的每個方法預設就是一個工作單元,這個是如何實作的呢?在方法上添加一個UnitOfWork特性也就讓該方法為一個工作單元,這又是如何實作的呢?上面的标題已然暴露了答案——動态代理
在ABP中,使用了Castle的DynamicProxy進行動态代理,在元件注冊是進行攔截器的注入,具體代碼如下:
internal static class UnitOfWorkRegistrar { public static void Initialize(IIocManager iocManager) {//該方法會在應用程式啟動的時候調用,進行事件注冊 iocManager.IocContainer.Kernel.ComponentRegistered += ComponentRegistered; } private static void ComponentRegistered(string key, IHandler handler) { if (UnitOfWorkHelper.IsConventionalUowClass(handler.ComponentModel.Implementation)) {//判斷類型是否實作了IRepository或IApplicationService,如果是,則為該類型注冊攔截器(UnitOfWorkInterceptor) handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor))); } else if (handler.ComponentModel.Implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(UnitOfWorkHelper.HasUnitOfWorkAttribute)) {//或者類型中任何一個方法上應用了UnitOfWorkAttribute,同樣為類型注冊攔截器(UnitOfWorkInterceptor) handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor))); } } }
public static bool IsConventionalUowClass(Type type) { return typeof(IRepository).IsAssignableFrom(type) || typeof(IApplicationService).IsAssignableFrom(type); }
攔截器UnitOfWorkInterceptor實作了IInterceptor接口,在調用注冊了攔截器的類的方法時,會被攔截下來,而去執行IInterceptor的Intercept方法,下面是Intercept方法的代碼實作:public static bool HasUnitOfWorkAttribute(MemberInfo methodInfo) { return methodInfo.IsDefined(typeof(UnitOfWorkAttribute), true); }
public void Intercept(IInvocation invocation) { if (_unitOfWorkManager.Current != null) {//如果目前已經在工作單元中,則直接執行被攔截類的方法 invocation.Proceed(); return; } //擷取方法上的UnitOfWorkAttribute,如果沒有傳回NULL,invocation.MethodInvocationTarget為被攔截類的類型 var unitOfWorkAttr = UnitOfWorkAttribute.GetUnitOfWorkAttributeOrNull(invocation.MethodInvocationTarget); if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled) {//如果目前方法上沒有UnitOfWorkAttribute或者是設定為Disabled,則直接調用被攔截類的方法 invocation.Proceed(); return; } //走到這裡就表示是需要将這個方法作為工作單元了,詳情點選檢視 PerformUow(invocation, unitOfWorkAttr.CreateOptions()); }
internal static UnitOfWorkAttribute GetUnitOfWorkAttributeOrNull(MemberInfo methodInfo) { //擷取方法上标記的UnitOfWorkAttribute var attrs = methodInfo.GetCustomAttributes(typeof(UnitOfWorkAttribute), false); if (attrs.Length > 0) { return (UnitOfWorkAttribute)attrs[0]; } if (UnitOfWorkHelper.IsConventionalUowClass(methodInfo.DeclaringType)) {//如果方法上沒有标記UnitOfWorkAttribute,但是方法的所屬類實作了IRepository或IApplicationService,則傳回一個預設UnitOfWorkAttribute return new UnitOfWorkAttribute(); } return null; }
private void PerformUow(IInvocation invocation, UnitOfWorkOptions options) { if (AsyncHelper.IsAsyncMethod(invocation.Method)) {//被攔截的方法為異步方法 PerformAsyncUow(invocation, options); } else {//被攔截的方法為同步方法 PerformSyncUow(invocation, options); } }
private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options) { //手動建立一個工作單元,将被攔截的方法直接放在工作單元中 using (var uow = _unitOfWorkManager.Begin(options)) { invocation.Proceed(); uow.Complete(); } }
private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options) { //異步方法的處理相對麻煩,需要将工作單元的Complete和Dispose放到異步任務中 var uow = _unitOfWorkManager.Begin(options); invocation.Proceed(); if (invocation.Method.ReturnType == typeof(Task)) {//如果是無傳回值的異步任務 invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( (Task)invocation.ReturnValue, async () => await uow.CompleteAsync(), exception => uow.Dispose() ); } else //Task<TResult> {//如果是有傳回值的異步任務 invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( invocation.Method.ReturnType.GenericTypeArguments[0], invocation.ReturnValue, async () => await uow.CompleteAsync(), (exception) => uow.Dispose() ); } }
/// <summary> /// 修改異步傳回結果,并且使用try...catch...finally /// </summary> /// <param name="actualReturnValue">方法原始傳回結果</param> /// <param name="postAction">期望傳回結果</param> /// <param name="finalAction">無論是否異常都将執行的代碼</param> /// <returns>新的異步結果</returns> public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction) { Exception exception = null; //在方法被攔截方法執行前調用工作單元的Begin,修改異步結果為調用工作單元的CompleteAsync方法,并保證工作單元會被Dispose掉 try { await actualReturnValue; await postAction(); } catch (Exception ex) { exception = ex; throw; } finally { finalAction(exception); } }
public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction) { //有傳回值的異步任務重寫更為複雜,需要先通過反射來為泛型傳值,然後才可調用泛型方法來重寫異步傳回值 return typeof (InternalAsyncHelper) .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) .MakeGenericMethod(taskReturnType) .Invoke(null, new object[] { actualReturnValue, action, finalAction }); }
public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction) { Exception exception = null; //該方法與之前無傳回值的異步任務調用的方法相同,隻是多了個泛型 try { var result = await actualReturnValue; await postAction(); return result; } catch (Exception ex) { exception = ex; throw; } finally { finalAction(exception); } }
總結來說,就是通過攔截器在執行方法的時候,先判斷是否需要進行工作單元操作。如果需要,則在執行方法前開啟工作單元,在執行方法後關閉工作單元。
在上面的代碼中,我們可以看到,工作單元都是通過_unitOfWorkManager(IUnitOfWorkManager)這樣一個對象進行的,下面我們就來解析這個類到底是如何進行單元控制的。
IUnitOfWorkManager、IUnitOfWorkCompleteHandle
ABP中,預設将UnitOfWorkManager作為IUnitOfWorkManager作為實作類,其實作中,Current直接取得ICurrentUnitOfWorkProvider對象的Current屬性,後續解析ICurrentUnitOfWorkProvider。而IUnitOfWorkManager的三個Begin隻是重載,最後都将調用第三個Begin的重載方法。下面是它的代碼實作:public interface IUnitOfWorkManager { IActiveUnitOfWork Current { get; } IUnitOfWorkCompleteHandle Begin(); IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope); IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options); }
public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options) { //為未指派的參數設定預設值 options.FillDefaultsForNonProvidedOptions(_defaultOptions); if (options.Scope == TransactionScopeOption.Required && _currentUnitOfWorkProvider.Current != null) {//如果目前Scope的設定為Required(而非RequiredNew),并且目前已存在工作單元,那麼久傳回下面這樣的一個對象 return new InnerUnitOfWorkCompleteHandle(); } //走到這裡,表示需要一個新的工作單元,通過IoC建立IUnitOfWork實作對象,然後開始工作單元,并設定此工作單元為目前工作單元 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); }; uow.Begin(options); _currentUnitOfWorkProvider.Current = uow; return uow; }
public IActiveUnitOfWork Current { get { return _currentUnitOfWorkProvider.Current; } }
Begin方法最後傳回的對象繼承自IUnitOfWorkCompleteHandle,讓我們看看IUnitOfWorkCompleteHandle的接口聲明又是什麼樣的:internal void FillDefaultsForNonProvidedOptions(IUnitOfWorkDefaultOptions defaultOptions) { if (!IsTransactional.HasValue) { IsTransactional = defaultOptions.IsTransactional; } if (!Scope.HasValue) { Scope = defaultOptions.Scope; } if (!Timeout.HasValue && defaultOptions.Timeout.HasValue) { Timeout = defaultOptions.Timeout.Value; } if (!IsolationLevel.HasValue && defaultOptions.IsolationLevel.HasValue) { IsolationLevel = defaultOptions.IsolationLevel.Value; } }
public interface IUnitOfWorkCompleteHandle : IDisposable { void Complete(); Task CompleteAsync(); }
總共也就兩個方法,而且意思相同,都是用來完成目前工作單元的,一個同步一個異步。同時實作了IDisposable接口,結合IUnitOfWorkManager使用Begin的方式便可了解其含義(使用using)。
在之前的Begin實作中,我們看到,其傳回路線有兩個,一個傳回了InnerUnitOfWorkCompleteHandle對象,另一個傳回了IUnitOfWork實作對象。IUnitOfWork稍後詳細解析,讓我們先來解析InnerUnitOfWorkCompleteHandle :
我們可以看到,Complete和CompleteAsync裡面,都隻是簡單的将_isCompleteCalled設定為true,在Dispose方法中,也僅僅隻是判斷是否已經dispose過、是否完成、是否有異常,沒有更多的動作。這樣大家會不會有一個疑問“這個工作單元的完成和釋放工作裡沒有具體的完成操作,怎麼就算完成工作單元了?”,這時,讓我們結合使用到InnerUnitOfWorkCompleteHandle的地方來看:internal class InnerUnitOfWorkCompleteHandle : IUnitOfWorkCompleteHandle { public const string DidNotCallCompleteMethodExceptionMessage = "Did not call Complete method of a unit of work."; private volatile bool _isCompleteCalled; private volatile bool _isDisposed; public void Complete() { _isCompleteCalled = true; } public async Task CompleteAsync() { _isCompleteCalled = true; } public void Dispose() { if (_isDisposed) { return; } _isDisposed = true; if (!_isCompleteCalled) { if (HasException()) { return; } throw new AbpException(DidNotCallCompleteMethodExceptionMessage); } } private static bool HasException() { try { return Marshal.GetExceptionCode() != 0; } catch (Exception) { return false; } } }
這個判斷,代表了目前已經存在工作單元,是以這個就是用于工作單元嵌套。内部的工作單元在送出和釋放時,不需要做實際的送出和釋放,隻需要保證沒有異常抛出,然後最外層工作單元再進行實際的送出和釋放。這也就說明,在Begin方法中的另一條路線,傳回IUnitOfWork實作對象才是最外層的事務對象。if (options.Scope == TransactionScopeOption.Required && _currentUnitOfWorkProvider.Current != null) { return new InnerUnitOfWorkCompleteHandle(); }
IUnitOfWork
IUnitOfWork除了繼承了IUnitOfWorkCompleteHandle接口,擁有了Complete方法外,還繼承了IActiveUnitOfWork接口:public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle { //唯一的辨別ID string Id { get; } //外層工作單元 IUnitOfWork Outer { get; set; } //開始工作單元 void Begin(UnitOfWorkOptions options); }
public interface IActiveUnitOfWork { //三個事件 event EventHandler Completed; event EventHandler<UnitOfWorkFailedEventArgs> Failed; event EventHandler Disposed; //工作單元設定 UnitOfWorkOptions Options { get; } //資料過濾配置 IReadOnlyList<DataFilterConfiguration> Filters { get; } bool IsDisposed { get; } void SaveChanges(); Task SaveChangesAsync(); IDisposable DisableFilter(params string[] filterNames); IDisposable EnableFilter(params string[] filterNames); bool IsFilterEnabled(string filterName); IDisposable SetFilterParameter(string filterName, string parameterName, object value); }
這個接口中包含了很多Filter相關的屬性與方法,都是些資料過濾相關的,這裡不對其進行介紹,是以這個接口裡,主要包含了三個事件(Completed、Failed、Disposed),工作單元設定(Options),IsDisposed以及同步異步的SaveChanges。
除了IUnitOfWork接口,ABP還提供了一個實作IUnitOfWork接口的抽象基類UnitOfWorkBase,UnitOfWorkBase的主要目的,是為開發人員處理一些前置、後置工作和異常處理,是以UnitOfWorkBase實作了部分方法,并提供了一些抽象方法,在實作的部分中做好前後置工作,然後調用抽象方法,将主要實作交給子類,并對其進行異常處理,下面以Begin實作與Complete實作為例:
public void Begin(UnitOfWorkOptions options) { if (options == null) { throw new ArgumentNullException("options"); } //防止Begin被多次調用 PreventMultipleBegin(); Options = options; //過濾配置 SetFilters(options.FilterOverrides); //抽象方法,子類實作 BeginUow(); } public void Complete() { //防止Complete被多次調用 PreventMultipleComplete(); try { //抽象方法,子類實作 CompleteUow(); _succeed = true; OnCompleted(); } catch (Exception ex) { _exception = ex; throw; } }
private void PreventMultipleBegin() { if (_isBeginCalledBefore) {//如果已經調用過Begin方法,再次調用則抛出異常 throw new AbpException("This unit of work has started before. Can not call Start method more than once."); } _isBeginCalledBefore = true; }
private void PreventMultipleComplete() { if (_isCompleteCalledBefore) {//如果已經掉用過Complete方法,再次調用則抛出異常 throw new AbpException("Complete is called before!"); } _isCompleteCalledBefore = true; }
UnitOfWorkBase的其他實作與上面的類似(非Unit of Work相關内容略去),便不全貼出。但是上面的代碼,大家有發現一個問題嗎? 就是那些PreventMultiple…方法的實作,用來防止多次Begin、Complete等。這些方法的實作,在單線程下是沒有問題的,但是裡面并沒有加鎖,是以不适用于多線程。這是作者的BUG嗎? 然而,這卻并不是BUG,而是設計如此,為何呢?因為在設計上,一個線程共用一個工作單元,也就不存在多線程了。關于這部分内容,後續将會介紹。
IUnitOfWorkManager目的是提供一個簡潔的IUnitOfWork管理對象,而IUnitOfWork則提供了整個工作單元需要的所有控制(Begin、SaveChanges、Complete、Dispose)。而具體應該如何保證一個線程共用一個工作單元,如何擷取目前的工作單元,則由ICurrentUnitOfWorkProvider進行管控,正如在解析UnitOfWorkManager時,說明了它的Current實際上就是調用ICurrentUnitOfWorkProvider實作對象的Current屬性。
ICurrentUnitOfWorkProvider
ICurrentUnitOfWorkProvider僅僅隻聲明了一個Current屬性,那麼重點讓我們來看看Current在實作類(CallContextCurrentUnitOfWorkProvider)中是如何寫的吧:public interface ICurrentUnitOfWorkProvider { IUnitOfWork Current { get; set; } }
上面标注的DoNotWire是為了不讓IoC進行屬性注入,Current内部分别調用了GetCurrentUow和SetCurrentUow,要取值,先要設值,讓我來先看看set吧:[DoNotWire] public IUnitOfWork Current { get { return GetCurrentUow(Logger); } set { SetCurrentUow(value, Logger); } }
private static void SetCurrentUow(IUnitOfWork value, ILogger logger) { if (value == null) {//如果在set的時候設定為null,便表示要退出目前工作單元 ExitFromCurrentUowScope(logger); return; } //擷取目前工作單元的key var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string; if (unitOfWorkKey != null) { IUnitOfWork outer; if (UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out outer)) { if (outer == value) { logger.Warn("Setting the same UOW to the CallContext, no need to set again!"); return; } //到這裡也就表示目前存在工作單元,那麼再次設定工作單元,不是替換掉目前的工作單元而是将目前工作單元作為本次設定的工作單元的外層工作單元 value.Outer = outer; } } unitOfWorkKey = value.Id; if (!UnitOfWorkDictionary.TryAdd(unitOfWorkKey, value)) {//如果向工作單元中添加工作單元失敗,便抛出異常 throw new AbpException("Can not set unit of work! UnitOfWorkDictionary.TryAdd returns false!"); } //設定目前線程的工作單元key CallContext.LogicalSetData(ContextKey, unitOfWorkKey); }
看完set,再讓我們來看看get吧:private static void ExitFromCurrentUowScope(ILogger logger) { //ContextKey為一個常量字元串 //CallContext可以了解為每個線程的獨有key,value集合類,每個線程都會有自己的存儲區, // 也就是線上程A中設定一個key為xx的value,線上程B中通過xx取不到,并可以存入相同鍵的value,但是不會互相覆寫、影響 //根據ContextKey從線程集合中取出目前工作單元key var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string; if (unitOfWorkKey == null) {//沒有取到值,表示目前無工作單元 logger.Warn("There is no current UOW to exit!"); return; } IUnitOfWork unitOfWork; //UnitOfWorkDictionary類型為ConcurrentDictionary,線程安全字典,用于存儲所有工作單元(單線程上最多隻能有一個工作單元,但是多線程可能會有多個) if (!UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out unitOfWork)) {//根據key沒有取到value,從線程集合(CallContext)中釋放該key CallContext.FreeNamedDataSlot(ContextKey); return; } //從工作單元集合中移除目前工作單元 UnitOfWorkDictionary.TryRemove(unitOfWorkKey, out unitOfWork); if (unitOfWork.Outer == null) {//如果目前工作單元沒有外層工作單元,則從線程集合(CallContext)中釋放該key CallContext.FreeNamedDataSlot(ContextKey); return; } var outerUnitOfWorkKey = unitOfWork.Outer.Id; //這裡也就表明了key實際上就是UnitOfWork的Id if (!UnitOfWorkDictionary.TryGetValue(outerUnitOfWorkKey, out unitOfWork)) {//如果目前工作單元有外層工作單元,但是從工作單元集合中沒有取到了外層工作單元,那麼同樣從線程集合(CallContext)中釋放該key CallContext.FreeNamedDataSlot(ContextKey); return; } //能到這裡,就表示目前工作單元有外層工作單元,并且從工作單元集合中擷取到了外層工作單元,那麼就設外層工作單元為目前工作單元 CallContext.LogicalSetData(ContextKey, outerUnitOfWorkKey); }
總的說來,所有的工作單元存儲線上程安全的字典對象中(ConcurrentDictionary),每個主線程共用一個工作單元的實作,通過線程集合(CallContext)實作。private static IUnitOfWork GetCurrentUow(ILogger logger) { //擷取目前工作單元key var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string; if (unitOfWorkKey == null) { return null; } IUnitOfWork unitOfWork; if (!UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out unitOfWork)) {//如果根據key擷取不到目前工作單元,那麼就從目前線程集合(CallContext)中釋放key CallContext.FreeNamedDataSlot(ContextKey); return null; } if (unitOfWork.IsDisposed) {//如果目前工作單元已經dispose,那麼就從工作單元集合中移除,并将key從目前線程集合(CallContext)中釋放 logger.Warn("There is a unitOfWorkKey in CallContext but the UOW was disposed!"); UnitOfWorkDictionary.TryRemove(unitOfWorkKey, out unitOfWork); CallContext.FreeNamedDataSlot(ContextKey); return null; } return unitOfWork; }
UnitOfWork實作
從上面的分析可以看出,ABP/Domain/Uow路徑下,主要隻是提供了一套抽象接口,并沒有提供實際的實作,IUnitOfWork最多也隻是提供了一個UnitOfWorkBase抽象類,這樣的自由性非常大,我非常喜歡這種方式。
當然ABP也另起了幾個項目來提供一些常用的ORM的Unit of Work封裝:
1.Ef: Abp.EntityFramework/EntityFramework/Uow
2.NH: Abp.NHibernate/NHibernate/Uow
3.Mongo: Abp.MongoDB/MongoDb/Uow
4.Memory: Abp.MemoryDb/MemoryDb/Uow
其中Mongo和Memory都沒有進行實質性的單元操作,Ef中使用TransactionScope進行單元操作,NH中使用ITransaction來進行單元操作。
ABP/Domain/Uow結構說明
UnitOfWorkRegistrar····································注冊攔截器,實作兩種預設的UnitOfWork,詳見最上面的預設行為
UnitOfWorkInterceptor··································Unit of Work攔截器,實作以AOP的方式進行注入單元控制
IUnitOfWorkManager····································簡潔的UnitOfWork管理對象
UnitOfWorkManager··································IUnitOfWorkManager預設實作
ICurrentUnitOfWorkProvider···························目前UnitOfWork管理對象
CallContextCurrentUnitOfWorkProvider············ICurrentUnitOfWorkProvider預設實作
IUnitOfWork···············································工作單元對象(Begin、SaveChanges、Complete、Dispose)
UnitOfWorkBase·······································IUnitOfWork抽象實作類,封裝實際操作的前後置操作及異常處理
NullUnitOfWork········································IUnitOfWork空實作
IActiveUnitOfWork·······································IUnitOfWork操作對象,不包含Begin與Complete操作
IUnitOfWorkCompleteHandle··························工作單元完成對象,用于實作繼承工作單元功能
InnerUnitOfWorkCompleteHandle··················IUnitOfWorkCompleteHandle實作之一,用于繼承外部工作單元
IUnitOfWorkDefaultOptions····························UnitOfWork預設設定
UnitOfWorkDefaultOptions··························IUnitOfWorkDefaultOptions預設實作
UnitOfWorkOptions·····································UnitOfWork配置對象
UnitOfWorkAttribute····································标記工作單元的特性
UnitOfWorkFailedEventArgs··························UnitOfWork的Failed事件參數
UnitOfWorkHelper·····································工具類
AbpDataFilters·········································資料過濾相關
DataFilterConfiguration·······························資料過濾相關