在上面一篇中我們主要是了解了在ABP系統中是如何使用UnitOfWork以及整個ABP系統中如何執行這些過程的,那么這一篇就讓我們來看看UnitOfWorkManager中在執行Begin和Complete方法中到底執行了些什么?還是和往常一樣來看看UnitOfWorkManager這個類,如果沒有讀過上面一篇,請點擊這里。
/// <summary>
/// Unit of work manager.
/// </summary>
internal class UnitOfWorkManager : IUnitOfWorkManager, ITransientDependency
{
private readonly IIocResolver _iocResolver;
private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
private readonly IUnitOfWorkDefaultOptions _defaultOptions;
public IActiveUnitOfWork Current
{
get { return _currentUnitOfWorkProvider.Current; }
}
public UnitOfWorkManager(
IIocResolver iocResolver,
ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
IUnitOfWorkDefaultOptions defaultOptions)
{
_iocResolver = iocResolver;
_currentUnitOfWorkProvider = currentUnitOfWorkProvider;
_defaultOptions = defaultOptions;
}
public IUnitOfWorkCompleteHandle Begin()
{
return Begin(new UnitOfWorkOptions());
}
public IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope)
{
return Begin(new UnitOfWorkOptions { Scope = scope });
}
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;
}
}
在分析這個類之前我們首先來看看這個類的初始化過程到底做了些什么?在這個類初始化中,注入了IIocResolver、ICurrentUnitOfWorkProvider、IUnitOfWorkDefaultOptions對於這三個接口,第一個IIocResolver應該比較熟悉了,主要是為了獲取IocContainer中特定接口對應的特定實例,IUnitOfWorkDefaultOptions這個在上一篇中已經說過了,這個主要是用於配置UnitOfWork中的一些關鍵屬性及其他配置項的構建,那么ICurrentUnitOfWorkProvider這個到底起什么作用呢?我們來看看這個接口的具體實現吧?
/// <summary>
/// CallContext implementation of <see cref="ICurrentUnitOfWorkProvider"/>.
/// This is the default implementation.
/// </summary>
public class AsyncLocalCurrentUnitOfWorkProvider : ICurrentUnitOfWorkProvider, ITransientDependency
{
/// <inheritdoc />
[DoNotWire]
public IUnitOfWork Current
{
get { return GetCurrentUow(); }
set { SetCurrentUow(value); }
}
public ILogger Logger { get; set; }
private static readonly AsyncLocal<LocalUowWrapper> AsyncLocalUow = new AsyncLocal<LocalUowWrapper>();
public AsyncLocalCurrentUnitOfWorkProvider()
{
Logger = NullLogger.Instance;
}
private static IUnitOfWork GetCurrentUow()
{
var uow = AsyncLocalUow.Value?.UnitOfWork;
if (uow == null)
{
return null;
}
if (uow.IsDisposed)
{
AsyncLocalUow.Value = null;
return null;
}
return uow;
}
private static void SetCurrentUow(IUnitOfWork value)
{
lock (AsyncLocalUow)
{
if (value == null)
{
if (AsyncLocalUow.Value == null)
{
return;
}
if (AsyncLocalUow.Value.UnitOfWork?.Outer == null)
{
AsyncLocalUow.Value.UnitOfWork = null;
AsyncLocalUow.Value = null;
return;
}
AsyncLocalUow.Value.UnitOfWork = AsyncLocalUow.Value.UnitOfWork.Outer;
}
else
{
if (AsyncLocalUow.Value?.UnitOfWork == null)
{
if (AsyncLocalUow.Value != null)
{
AsyncLocalUow.Value.UnitOfWork = value;
}
AsyncLocalUow.Value = new LocalUowWrapper(value);
return;
}
value.Outer = AsyncLocalUow.Value.UnitOfWork;
AsyncLocalUow.Value.UnitOfWork = value;
}
}
}
private class LocalUowWrapper
{
public IUnitOfWork UnitOfWork { get; set; }
public LocalUowWrapper(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
}
}
這個類中最核心的就是GetCurrentUow()和SetCurrentUow(IUnitOfWork value)這兩個方法了,初一看還是不太理解,這里我們可以看看ABP的官方文檔上面有這么一段描述:
A Unit Of Work Method Calls Another
The unit of work is ambient. If a unit of work method calls another unit of work method, they share the same connection and transaction. The first method manages the connection and then the other methods reuse it.這段話的意思就是如果一個帶工作單元的方法調用了另外一個帶工作單元的方法的時候,那么這兩個方法是會共享相同的連接和事物的,並且第一個調用的方法管理這個工作單元,第二個進行復用,所以上面的這個ICurrentUnitOfWorkProvider接口主要是為了解決多個工作單元互相調用的問題。
但是我們來結合UnitOfWorkManager中的Begin方法試着來一起理解這個類。
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;
}
在這個方法中,首先會獲取_currentUnitOfWorkProvider.Current即獲取當前執行方法唯一的UnitOfWork,如果當前方法的UnitOfWork不為null,那么就簡單返回一個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 Task CompleteAsync()
{
_isCompleteCalled = true;
return Task.FromResult(0);
}
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;
}
}
}
其實這個也很好理解,如果一個UnitOfWork的內部再調用另外一個UnitOfWork的時候,那么第二個UnitOfWork內部在執行_currentUnitOfWorkProvider.Current的時候,那么獲取到的結果一定不為null,所以當第二個UnitOfWork執行完畢的時候只需要執行簡單的設置_isCompleteCalled=true的操作就可以了,如果是第一個工作單元執行這個Begin方法時,當執行_currentUnitOfWorkProvider.Current的時候是無法獲取到特定的工作單元的,再在后面會通過 var uow = _iocResolver.Resolve<IUnitOfWork>()來獲取一個UnitOfWorkWork的對象,然后再執行_currentUnitOfWorkProvider.Current = uow這樣第一個執行Begin方法的UnitOfWork也就獲取到了一個唯一的IUnitOfWork的實例,並且其內部調用的工作單元也只會共享這一個唯一的IUnitOfWork的實例,這段代碼我覺得真的非常巧妙,永遠保證了嵌套調用的時候第一個方法和內部的方法擁有唯一的一個UnitOfWork,並且只有在第一個UnitOfWork退出的時候才執行后續的一系類操作。
在這里我們發現最后的核心邏輯還是在唯一的繼承自IUnitOfWork的對象中來完成和數據庫的一些連接及事物操作,由於在ABP框架中繼承自IUnitOfWork的對象眾多,這里只以EntityFrameworkCore來進行說明,其它一些MongoDBUnitOfWork這里就不再作為分析的重點。這個IUnitOfWork中定義的方法是在UnitOfWorkBase中操作的,由於這里內容太多,僅僅貼出關鍵代碼。
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();
}
這個里面主要是進行一些IReadOnlyList<DataFilterConfiguration> Filters的賦值一些操作,還有設置TenantId等操作,最后執行的BeginUow是一個虛方法,不同的ORM框架會去重載這個方法,然后完成相應的一些數據庫連接以及數據庫事物的操作。這里我們重點看一看EfCoreUnitOfWork這個類里面做了些什么?
/// <summary>
/// Implements Unit of work for Entity Framework.
/// </summary>
public class EfCoreUnitOfWork : UnitOfWorkBase, ITransientDependency
{
protected IDictionary<string, DbContext> ActiveDbContexts { get; }
protected IIocResolver IocResolver { get; }
private readonly IDbContextResolver _dbContextResolver;
private readonly IDbContextTypeMatcher _dbContextTypeMatcher;
private readonly IEfCoreTransactionStrategy _transactionStrategy;
/// <summary>
/// Creates a new <see cref="EfCoreUnitOfWork"/>.
/// </summary>
public EfCoreUnitOfWork(
IIocResolver iocResolver,
IConnectionStringResolver connectionStringResolver,
IUnitOfWorkFilterExecuter filterExecuter,
IDbContextResolver dbContextResolver,
IUnitOfWorkDefaultOptions defaultOptions,
IDbContextTypeMatcher dbContextTypeMatcher,
IEfCoreTransactionStrategy transactionStrategy)
: base(
connectionStringResolver,
defaultOptions,
filterExecuter)
{
IocResolver = iocResolver;
_dbContextResolver = dbContextResolver;
_dbContextTypeMatcher = dbContextTypeMatcher;
_transactionStrategy = transactionStrategy;
ActiveDbContexts = new Dictionary<string, DbContext>();
}
protected override void BeginUow()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.InitOptions(Options);
}
}
public override void SaveChanges()
{
foreach (var dbContext in GetAllActiveDbContexts())
{
SaveChangesInDbContext(dbContext);
}
}
public override async Task SaveChangesAsync()
{
foreach (var dbContext in GetAllActiveDbContexts())
{
await SaveChangesInDbContextAsync(dbContext);
}
}
protected override void CompleteUow()
{
SaveChanges();
CommitTransaction();
}
protected override async Task CompleteUowAsync()
{
await SaveChangesAsync();
CommitTransaction();
}
private void CommitTransaction()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.Commit();
}
}
public IReadOnlyList<DbContext> GetAllActiveDbContexts()
{
return ActiveDbContexts.Values.ToImmutableList();
}
public virtual TDbContext GetOrCreateDbContext<TDbContext>(MultiTenancySides? multiTenancySide = null)
where TDbContext : DbContext
{
var concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext));
var connectionStringResolveArgs = new ConnectionStringResolveArgs(multiTenancySide);
connectionStringResolveArgs["DbContextType"] = typeof(TDbContext);
connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType;
var connectionString = ResolveConnectionString(connectionStringResolveArgs);
var dbContextKey = concreteDbContextType.FullName + "#" + connectionString;
DbContext dbContext;
if (!ActiveDbContexts.TryGetValue(dbContextKey, out dbContext))
{
if (Options.IsTransactional == true)
{
dbContext = _transactionStrategy.CreateDbContext<TDbContext>(connectionString, _dbContextResolver);
}
else
{
dbContext = _dbContextResolver.Resolve<TDbContext>(connectionString, null);
}
if (Options.Timeout.HasValue &&
dbContext.Database.IsRelational() &&
!dbContext.Database.GetCommandTimeout().HasValue)
{
dbContext.Database.SetCommandTimeout(Options.Timeout.Value.TotalSeconds.To<int>());
}
ActiveDbContexts[dbContextKey] = dbContext;
}
return (TDbContext)dbContext;
}
protected override void DisposeUow()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.Dispose(IocResolver);
}
else
{
foreach (var context in GetAllActiveDbContexts())
{
Release(context);
}
}
ActiveDbContexts.Clear();
}
protected virtual void SaveChangesInDbContext(DbContext dbContext)
{
dbContext.SaveChanges();
}
protected virtual async Task SaveChangesInDbContextAsync(DbContext dbContext)
{
await dbContext.SaveChangesAsync();
}
protected virtual void Release(DbContext dbContext)
{
dbContext.Dispose();
IocResolver.Release(dbContext);
}
}
在這個類中,有兩個方法是非常重要的,一個是BeginUow(),這個在執行UnitOfWorkManager的Begin方法的時候會使用到,另外一個就是 CompleteUow(),這個方法會在執行完攔截方法后UnitOfWorkManager執行Complete方法時候最終調用這個CompleteUow方法,在這個方法內部會依次執行SaveChanges()和CommitTransaction()方法,這兩個方法也是非常好理解的,在SaveChanges方法內部會獲取所有處於活動狀態的DBContext,然后執行里面的每一個SaveChanges()從而完成更新到數據庫的操作,CommitTransaction()顧名思義就是提交數據庫事物的操作,有了這些操作一個帶事物的並且能夠管理數據庫打開和關閉操作的UnitOfWork過程就整個都結束了。
在BeginUow中我們首先判斷Options.IsTransactional == true,這個Options是我們在執行IUnitOfWork.Begin方法中傳遞過來的UnitOfWorkOptions,這個主要用於配置UnitOfWork的一些參數,那么這個參數有什么意義呢?我們發現,在執行CommitTransaction()也是用的這個判斷,所以這個參數可以控制當前的UnitOfWork是否執行事務,其實這個在ABP官方文檔中也有這個方面的論述,我們可以看一看。
Non-Transactional Unit Of Work
By its nature, a unit of work is transactional. ASP.NET Boilerplate starts, commits or rolls back an explicit database-level transaction. In some special cases, the transaction may cause problems since it may lock some rows or tables in the database. In these situations, you may want to disable the database-level transaction. The UnitOfWork attribute can get a boolean value in its constructor to work as non-transactional.
那么我們在ABP中如果想在UnitOfWork中禁用事務那么我們應該怎么做呢?同樣我們可以直接在自定義UnitOfWork屬性中添加IsTransactional:false來進行禁用操作。
[UnitOfWork(isTransactional: false)]
public GetTasksOutput GetTasks(GetTasksInput input)
{
var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
return new GetTasksOutput
{
Tasks = Mapper.Map<List<TaskDto>>(tasks)
};
}
最后,點擊這里返回整個ABP系列的主目錄。
