ABP
ABP是「ASP.NET Boilerplate Project」的簡稱。javascript
ABP的官方網站:http://www.aspnetboilerplate.comhtml
ABP在Github上的開源項目:https://github.com/aspnetboilerplatejava
ABP其餘學習博客推薦及介紹:http://www.cnblogs.com/mienreal/p/4528470.htmlgit
ABP中Unit of Work概念及使用
若是這是你首次接觸ABP框架或ABP的Unit of Work,推薦先看看 ABP使用及框架解析系列-[Unit of Work part.1-概念及使用]github
舒適提示
1.ABP的Unit of Work相關代碼路徑爲:/Abp/Domain/Uow安全
2.框架實現中,代碼不會貼所有的,可是會說明代碼在項目中的位置,而且爲了更加直觀和縮短篇幅,對代碼更細緻的註釋,直接在代碼中,不要忘記看代碼註釋哈,。多線程
3.博客中的代碼,有一些方法是能夠點擊的!框架
動態代理/攔截器/AOP
上面講到Unit of Work有兩個默認實現,領域服務和倉儲庫的每一個方法默認就是一個工做單元,這個是如何實現的呢?在方法上添加一個UnitOfWork特性也就讓該方法爲一個工做單元,這又是如何實現的呢?上面的標題已然暴露了答案——動態代理異步
在ABP中,使用了Castle的DynamicProxy進行動態代理,在組件註冊是進行攔截器的注入,具體代碼以下:async
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·······························數據過濾相關