工作单元是维护受业务影响的对象的列表,并维护变化写入和并发问题的解决
大概的意思是说,对多个操作进行打包,记录对象上的所有变化,并在最后提交时一次性将所有变化通过系统事务写入数据库。
工作单元对并发的协调,是依靠聚合根上的乐观离线锁,以及数据库事务的并发控制能力来共同完成的。
Datatable是旧的工作单元,DataTable的AcceptChanges方法,它已经淘汰,随着 Code First方法
所有修改就保存到数据库了新一代的工作单元DbContext成为数据访问的中心,它要求尽晚打开,尽早释放。由工作单元进行控制。
工作单元,它拥有IDatabaseApiContainer,ITransactionApiContainer
它的CompleteAsync,是调用databaseApi一次写入。
public virtual async Task CompleteAsync(CancellationToken cancellationToken = default) { if (_isRolledback) { return; } PreventMultipleComplete(); try { _isCompleting = true; await SaveChangesAsync(cancellationToken); await CommitTransactionsAsync(); IsCompleted = true; await OnCompletedAsync(); } catch (Exception ex) { _exception = ex; throw; } } public virtual void Dispose() { if (IsDisposed) { return; } IsDisposed = true; DisposeTransactions(); if (!IsCompleted || _exception != null) { OnFailed(); } OnDisposed(); }
1、拦截器
实现类型 (implementationType
) 或类型的任一方法标注了 UnitOfWork
特性的话,要继承IUnitOfWorkEnabled,这三种任一种情况都添加拦截器
IUnitOfWorkEnabled的派生:ApplicationService、BasicRepositoryBase<TEntity> 、DapperRepository<TDbContext>
public override async Task InterceptAsync(IAbpMethodInvocation invocation) { if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute)) { await invocation.ProceedAsync(); return; } using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute))) { await invocation.ProceedAsync(); await uow.CompleteAsync(); } }
2、创建工作单元
最开始是null,新建一工作单元1,此时为当前工作单元,outer为null
再创建一个工作单元2,此工作单元是当前工作单元,outer为上一个工作单元1,而工作单元1的 outer为null
注意的是,如果当前工作单元属性为IsReserved、IsDisposed、IsCompleted,工作单元指向outer
当前工作单元不为空,并且不明确requiresNew》创建子工作单元
public IUnitOfWork Begin(AbpUnitOfWorkOptions options, bool requiresNew = false) { Check.NotNull(options, nameof(options)); var currentUow = Current; if (currentUow != null && !requiresNew) { return new ChildUnitOfWork(currentUow); } var unitOfWork = CreateNewUnitOfWork(); unitOfWork.Initialize(options); return unitOfWork; } private IUnitOfWork CreateNewUnitOfWork() { var scope = _serviceScopeFactory.CreateScope(); try { var outerUow = _ambientUnitOfWork.UnitOfWork; var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>(); unitOfWork.SetOuter(outerUow); _ambientUnitOfWork.SetUnitOfWork(unitOfWork); unitOfWork.Disposed += (sender, args) => { _ambientUnitOfWork.SetUnitOfWork(outerUow); scope.Dispose(); }; return unitOfWork; } catch { scope.Dispose(); throw; } }
创建工作单元,作为 属性的AbpUnitOfWorkOptions,它记录是ITransational,IsolationLevel,TimeOut,它需要执行unitofwork.initialize()
而Timout可用于创建数据库连接的dbContext.DataBase.SetCommandTimeOut()
它不使用容器进行注入,而是使用得AbpUnitOfWorkDefaultOptions进行赋值的,它是全局默认的,其 Normalize方法,如果全局设置,则优先使用此处值。
否则使用传入的值,而传入的值 ,可以根据UnitOfWork的属性赋值,如果它的方法名是Get,而UnitOfWorkTransationalBehavior的值l 默认是Auto,则ITansational为false
其它则为True
有两个,一个是IDataBaseApiContainer,另一个是ITansationalApiContainer
而IDataBaseApi的实现者之一,EfCoreDataBaseApi是支持ISuportsSavingChanges
而ITansactionApi的实现者之一,EfCoreDataBaseApi是支持ISupportRollback
3、GetDbContext 数据库上下文对象
IDbContextProvider<TDbContext> TDbContext GetDbContext();
IMemoryDatabaseProvider<TMemoryDbContext>
IMongoDbContextProvider<TMongoDbContext>
DbContextProvider 的具体实现,可以看到他是通过 IUnitOfWorkManager
(工作单元管理器) 得到可用的工作单元,然后通过工作单元提供的 IServiceProvider
解析所需要的数据库上下文对象。仓储使用到的数据库上下文对象是通过工作单元的 IServiceProvider
进行解析的
工作单元的 ServiceProvider
是通过继承 IServiceProviderAccessor
得到的,也就是说在构建工作单元的时候,这个 Provider 就是工作单元管理器创建的子容器。
DbContext 是通过工作单元的 ServiceProvider
创建的,当工作单元被释放的时候,也会连带这个子容器被释放。那么我们之前解析出来的 DbContext ,也就会随着子容器的释放而被释放
工作单元是什么时候被释放的呢...因为拦截器默认是为仓储建立了拦截器,所以在获得到 DbContext 的时候,拦截器已经将之前的 DbContext 释放掉了。
public override void Intercept(IAbpMethodInvocation invocation) { if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute)) { invocation.Proceed(); return; } using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute))) { invocation.Proceed(); uow.Complete(); } }
解决方法:通过 [UnitOfWork]
特性或者 IUnitOfManager.Begin
开启一个新的工作单元即可。
这样任意地方注入一个 IRepository<T,TKey>
仓储,调用 IRepository<T,TKey>.ToList()
方法,容器创建IRepository实例,首先调用拦截器,判断此方法要使用UnitOfWork,则创建子工作单元,完成,再到整个方法完成。
//Trying to begin a reserved UOW by AbpUnitOfWorkMiddleware if (_unitOfWorkManager.TryBeginReserved(AbpUnitOfWorkMiddleware.UnitOfWorkReservationName, options)) { var result = await next(); if (!Succeed(result)) { await RollbackAsync(context); } return; } //Begin a new, independent unit of work using (var uow = _unitOfWorkManager.Begin(options)) { var result = await next(); if (Succeed(result)) { await uow.CompleteAsync(context.HttpContext.RequestAborted); } }