前言
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); }public static bool HasUnitOfWorkAttribute(MemberInfo methodInfo) { return methodInfo.IsDefined(typeof(UnitOfWorkAttribute), true); }攔截器UnitOfWorkInterceptor實現了IInterceptor接口,在調用注冊了攔截器的類的方法時,會被攔截下來,而去執行IInterceptor的Intercept方法,下面是Intercept方法的代碼實現:
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
public interface IUnitOfWorkManager { IActiveUnitOfWork Current { get; } IUnitOfWorkCompleteHandle Begin(); IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope); IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options); }ABP中,默認將UnitOfWorkManager作為IUnitOfWorkManager作為實現類,其實現中,Current直接取得ICurrentUnitOfWorkProvider對象的Current屬性,后續解析ICurrentUnitOfWorkProvider。而IUnitOfWorkManager的三個Begin只是重載,最后都將調用第三個Begin的重載方法。下面是它的代碼實現:
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; } }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; } }Begin方法最后返回的對象繼承自IUnitOfWorkCompleteHandle,讓我們看看IUnitOfWorkCompleteHandle的接口聲明又是什么樣的:
public interface IUnitOfWorkCompleteHandle : IDisposable { void Complete(); Task CompleteAsync(); }總共也就兩個方法,而且意思相同,都是用來完成當前工作單元的,一個同步一個異步。同時實現了IDisposable接口,結合IUnitOfWorkManager使用Begin的方式便可理解其含義(使用using)。
在之前的Begin實現中,我們看到,其返回路線有兩個,一個返回了InnerUnitOfWorkCompleteHandle對象,另一個返回了IUnitOfWork實現對象。IUnitOfWork稍后詳細解析,讓我們先來解析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; } } }我們可以看到,Complete和CompleteAsync里面,都只是簡單的將_isCompleteCalled設置為true,在Dispose方法中,也僅僅只是判斷是否已經dispose過、是否完成、是否有異常,沒有更多的動作。這樣大家會不會有一個疑問“這個工作單元的完成和釋放工作里沒有具體的完成操作,怎么就算完成工作單元了?”,這時,讓我們結合使用到InnerUnitOfWorkCompleteHandle的地方來看:
if (options.Scope == TransactionScopeOption.Required && _currentUnitOfWorkProvider.Current != null) { return new InnerUnitOfWorkCompleteHandle(); }這個判斷,代表了當前已經存在工作單元,所以這個就是用於工作單元嵌套。內部的工作單元在提交和釋放時,不需要做實際的提交和釋放,只需要保證沒有異常拋出,然后最外層工作單元再進行實際的提交和釋放。這也就說明,在Begin方法中的另一條路線,返回IUnitOfWork實現對象才是最外層的事務對象。
IUnitOfWork
public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle { //唯一的標識ID string Id { get; } //外層工作單元 IUnitOfWork Outer { get; set; } //開始工作單元 void Begin(UnitOfWorkOptions options); }IUnitOfWork除了繼承了IUnitOfWorkCompleteHandle接口,擁有了Complete方法外,還繼承了IActiveUnitOfWork接口:
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
public interface ICurrentUnitOfWorkProvider { IUnitOfWork Current { get; set; } }ICurrentUnitOfWorkProvider僅僅只聲明了一個Current屬性,那么重點讓我們來看看Current在實現類(CallContextCurrentUnitOfWorkProvider)中是如何寫的吧:
[DoNotWire] public IUnitOfWork Current { get { return GetCurrentUow(Logger); } set { SetCurrentUow(value, Logger); } }上面標注的DoNotWire是為了不讓IoC進行屬性注入,Current內部分別調用了GetCurrentUow和SetCurrentUow,要取值,先要設值,讓我來先看看set吧:
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); }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); }看完set,再讓我們來看看get吧:
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; }總的說來,所有的工作單元存儲在線程安全的字典對象中(ConcurrentDictionary),每個主線程共用一個工作單元的實現,通過線程集合(CallContext)實現。
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·······························數據過濾相關