在上面一篇中我们主要是了解了在ABP系统中是如何使用UnitOfWork以及整个ABP系统中如何执行这些过程的,那么这一篇就让我们来看看UnitOfWorkManager中在执行Begin和Complete方法中到底执行了些什么?还是和往常一样来看看UnitOfWorkManager这个类,如果没有读过上面一篇,请点击。
////// Unit of work manager. /// 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(); 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这个到底起什么作用呢?我们来看看这个接口的具体实现吧?
////// CallContext implementation of public class AsyncLocalCurrentUnitOfWorkProvider : ICurrentUnitOfWorkProvider, ITransientDependency { ///. /// This is the default implementation. /// [DoNotWire] public IUnitOfWork Current { get { return GetCurrentUow(); } set { SetCurrentUow(value); } } public ILogger Logger { get; set; } private static readonly AsyncLocal AsyncLocalUow = new AsyncLocal (); 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(); 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这个类里面做了些什么?
////// Implements Unit of work for Entity Framework. /// public class EfCoreUnitOfWork : UnitOfWorkBase, ITransientDependency { protected IDictionaryActiveDbContexts { get; } protected IIocResolver IocResolver { get; } private readonly IDbContextResolver _dbContextResolver; private readonly IDbContextTypeMatcher _dbContextTypeMatcher; private readonly IEfCoreTransactionStrategy _transactionStrategy; /// /// Creates a new 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. /// (); } 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 GetAllActiveDbContexts() { return ActiveDbContexts.Values.ToImmutableList(); } public virtual TDbContext GetOrCreateDbContext (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 (connectionString, _dbContextResolver); } else { dbContext = _dbContextResolver.Resolve (connectionString, null); } if (Options.Timeout.HasValue && dbContext.Database.IsRelational() && !dbContext.Database.GetCommandTimeout().HasValue) { dbContext.Database.SetCommandTimeout(Options.Timeout.Value.TotalSeconds.To ()); } 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是否执行事务,其实这个在中也有这个方面的论述,我们可以看一看。
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
>(tasks) };}
最后,点击返回整个ABP系列的主目录。