摘要:工作单元(Unit of Work) 是一种软件设计模式,它维护一个受业务事务影响的对象列表,并协调变更的写入和解决并发问题,以确保所有变更都在单一事务内完成。
工作单元(Unit of Work) 是一种软件设计模式,它维护一个受业务事务影响的对象列表,并协调变更的写入和解决并发问题,以确保所有变更都在单一事务内完成。
工作单元的主要职责之一是管理数据库事务。它提供以下事务管理功能:
自动管理数据库连接和事务范围,消除手动控制事务的需求
确保业务操作的完整性,使所有数据库操作要么成功,要么完全回滚
支持配置事务隔离级别和超时周期
支持嵌套事务和事务传播
事务行为 默认事务设置你可以通过以下配置来修改默认行为:
Configure(options =>{
/*
修改所有工作单元的默认事务行为:
- UnitOfWorkTransactionBehavior.Enabled: 始终启用事务,所有请求都会启动一个事务
- UnitOfWorkTransactionBehavior.Disabled: 始终禁用事务,所有请求都不会启动事务
- UnitOfWorkTransactionBehavior.Auto: 根据 HTTP 请求类型自动决定是否启动事务
*/
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
// 设置默认超时
options.Timeout = TimeSpan.FromSeconds(30);
// 设置默认隔离级别
options.IsolationLevel = IsolationLevel.ReadCommitted;
});
自动事务管理
ABP 框架通过中间件、MVC 全局过滤器和拦截器实现工作单元和事务的自动管理。在大多数情况下,你不需要手动管理这些事务。
HTTP 请求的事务行为默认情况下,框架为 HTTP 请求采用智能事务管理策略:
GET请求不会启动事务性工作单元,因为没有数据修改
其他 HTTP 请求(POST/PUT/DELETE等)会启动事务性工作单元
手动事务控制如果你需要手动启动一个新的工作单元,可以自定义是否启动事务,并设置事务的隔离级别和超时:
// 启动一个事务性工作单元using(varuow = _unitOfWorkManager.Begin(
isTransactional:true
isolationLevel: IsolationLevel.RepeatableRead,
timeout:30
))
{
// 在事务内执行数据库操作
awaituow.CompleteAsync;
}
// 启动一个非事务性工作单元
using(varuow = _unitOfWorkManager.Begin(
isTransactional:false
))
{
// 在没有事务的情况下执行数据库操作
awaituow.CompleteAsync;
}
使用 [UnitOfWork] 特性配置事务你可以通过在方法、类或接口上使用来自定义事务行为:非事务性工作单元在某些场景下,你可能不需要事务支持。你可以通过设置IsTransactional = false来创建一个非事务性的工作单元:publicvirtualasyncTaskImportDataAsync(List items)
{
using(varuow = _unitOfWorkManager.Begin(
isTransactional:false
))
{
foreach(variteminitems)
{
await_repository.InsertAsync(item, autoSave:true);
// 每次 InsertAsync 都会立即保存到数据库
// 如果后续操作失败,已保存的数据将无法回滚
}
awaituow.CompleteAsync;
}
}
适用场景:
允许部分成功的批量数据导入场景
只读操作,如查询
对数据一致性要求较低的场景
提交事务的方法在事务性工作单元中工作单元提供了几种方法来提交数据库的更改:
IUnitOfWork.SaveChangesAsync
await_unitOfWorkManager.Current.SaveChangesAsync;仓储中的 autoSave 参数
await_repository.InsertAsync(entity, autoSave:true);autoSave和SaveChangesAsync都会在当前上下文中提交更改到数据库。然而,直到调用CompleteAsync时,这些更改才会生效。如果工作单元抛出异常或没有调用CompleteAsync,事务将会回滚。也就是说,所有数据库操作都会被撤销。只有成功执行CompleteAsync后,事务才会永久提交到数据库。
CompleteAsync 方法
using(varuow = _unitOfWorkManager.Begin){
// 执行数据库操作
awaituow.CompleteAsync;
}
当你手动控制工作单元时,CompleteAsync方法对事务完成至关重要。工作单元内部维护了一个DbTransaction对象,CompleteAsync方法会调用DbTransaction.CommitAsync来提交事务。如果CompleteAsync没有执行或者执行失败,事务将不会被提交。
此方法不仅会提交所有数据库事务,还会:
执行并处理工作单元中的所有待处理领域事件
执行工作单元中所有已注册的后操作和清理任务
在处置工作单元对象时释放所有 DbTransaction资源
注意:CompleteAsync 方法只能调用一次,不支持多次调用。在非事务性工作单元中在非事务性工作单元中,这些方法的行为有所不同:
autoSave和会立即将更改持久化到数据库中,且这些更改无法回滚。即使在非事务性工作单元中,调用CompleteAsync方法仍然是必要的,因为它处理其他必要的任务。示例:
using(varuow = _unitOfWorkManager.Begin(isTransactional:false)){
// 更改会立即持久化,且无法回滚
await_repository.InsertAsync(entity1, autoSave:true);
// 此操作会独立于前一个操作持久化
await_repository.InsertAsync(entity2, autoSave:true);
awaituow.CompleteAsync;
}
回滚事务的方法在事务性工作单元中
工作单元提供了多种回滚事务的方法:
自动回滚
对于由 ABP 框架自动管理的事务,任何在请求过程中未捕获的异常将触发自动回滚。
手动回滚
对于手动管理的事务,你可以显式调用RollbackAsync方法来立即回滚当前事务。重要:一旦调用了 RollbackAsync,整个工作单元事务将会立即回滚,任何后续对 CompleteAsync 的调用将没有效果。using(varuow = _unitOfWorkManager.Begin(isTransactional:true
isolationLevel: IsolationLevel.RepeatableRead,
timeout:30
))
{
await_repository.InsertAsync(entity);
if(someCondition)
{
awaituow.RollbackAsync;
return;
}
awaituow.CompleteAsync;
}
CompleteAsync方法尝试提交事务。如果在此过程中发生任何异常,事务将不会提交。
以下是两种常见的异常场景:
在工作单元内处理异常
using(varuow = _unitOfWorkManager.Begin(isTransactional:true
isolationLevel: IsolationLevel.RepeatableRead,
timeout:30
))
{
try
{
await_bookRepository.InsertAsync(book);
awaituow.SaveChangesAsync;
await_productRepository.UpdateAsync(product);
awaituow.CompleteAsync;
}
catch(Exception)
{
// 异常可能发生在 InsertAsync、SaveChangesAsync、UpdateAsync 或 CompleteAsync 中
// 即使某些操作成功,事务仍然未提交到数据库
// 虽然可以显式调用 RollbackAsync 来回滚事务,
// 但如果 CompleteAsync 执行失败,事务仍然不会提交
throw;
}
}
在工作单元外处理异常
try{
using(varuow = _unitOfWorkManager.Begin(
isTransactional:true
isolationLevel: IsolationLevel.RepeatableRead,
timeout:30
))
{
await_bookRepository.InsertAsync(book);
awaituow.SaveChangesAsync;
await_productRepository.UpdateAsync(product);
awaituow.CompleteAsync;
}
}
catch(Exception)
{
// 异常可能发生在 UpdateAsync、SaveChangesAsync、UpdateAsync 或 CompleteAsync 中
// 即使某些操作成功,事务仍然未提交到数据库
// 由于 CompleteAsync 未成功执行,事务不会被提交
throw;
}
在非事务性工作单元中在非事务性工作单元中,操作是不可逆的。使用autoSave: true保存的更改会立即持久化,并且无法回滚。此时调用RollbackAsync方法没有效果。事务管理最佳实践 1. 记得提交事务在手动控制事务时,记得在操作完成后调用CompleteAsync方法提交事务。2. 注意上下文如果当前上下文中已经存在工作单元,UnitOfWorkManager.Begin方法和UnitOfWorkAttributerequiresNew: true来强制创建一个新的工作单元。[UnitOfWork]
publicasyncTaskMethod1
{
using(varuow = _unitOfWorkManager.Begin(
requiresNew:true
isTransactional:true
isolationLevel: IsolationLevel.RepeatableRead,
timeout:30
))
{
awaitMethod2;
awaituow.CompleteAsync;
}
}
3. 使用 virtual 方法要使用工作单元特性,必须在依赖注入类服务中的方法上使用virtual修饰符,因为 ABP 框架使用拦截器,而它无法拦截非virtual方法,因此无法实现工作单元功能。4. 避免长时间事务
启用长时间运行的事务可能导致资源锁定、事务日志使用过多,并降低并发性能,同时回滚成本高,可能会耗尽数据库连接资源。建议将事务拆分为较短的事务,减少锁持有时间,优化性能和可靠性。
事务相关建议根据业务需求选择合适的事务隔离级别
避免过长的事务,长时间运行的操作应拆分为多个小事务
合理使用 requiresNew参数来控制事务边界
注意设置合适的事务超时时间
确保事务在发生异常时能够正确回滚
对于只读操作,建议使用非事务性工作单元以提高性能
参考资料ABP 工作单元 https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
EF Core 事务 https://docs.microsoft.com/en-us/ef/core/saving/transactions
事务隔离级别 https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel
来源:opendotnet