目录
工作单元(Unit of Work) 是一种软件设计模式,它维护一个受业务事务影响的对象列表,并协调变更的写入和解决并发问题,以确保所有变更都在单一事务内完成。
事务管理概述
工作单元的主要职责之一是管理数据库事务。它提供以下事务管理功能:
-
自动管理数据库连接和事务范围,消除手动控制事务的需求
-
确保业务操作的完整性,使所有数据库操作要么成功,要么完全回滚
-
支持配置事务隔离级别和超时周期
-
支持嵌套事务和事务传播
事务行为
默认事务设置
你可以通过以下配置来修改默认行为:
Configure<AbpUnitOfWorkDefaultOptions>(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 (var uow = _unitOfWorkManager.Begin(
isTransactional: true,
isolationLevel: IsolationLevel.RepeatableRead,
timeout: 30
))
{
// 在事务内执行数据库操作
await uow.CompleteAsync();
}
// 启动一个非事务性工作单元
using (var uow = _unitOfWorkManager.Begin(
isTransactional: false
))
{
// 在没有事务的情况下执行数据库操作
await uow.CompleteAsync();
}
使用 [UnitOfWork]
特性配置事务
你可以通过在方法、类或接口上使用 UnitOfWorkAttribute
来自定义事务行为:
[UnitOfWork(
IsTransactional = true,
IsolationLevel = IsolationLevel.RepeatableRead,
Timeout = 30
)]
public virtual async Task ProcessOrderAsync(int orderId)
{
// 在事务内执行数据库操作
}
非事务性工作单元
在某些场景下,你可能不需要事务支持。你可以通过设置 IsTransactional = false
来创建一个非事务性的工作单元:
public virtual async Task ImportDataAsync(List<DataItem> items)
{
using (var uow = _unitOfWorkManager.Begin(
isTransactional: false
))
{
foreach (var item in items)
{
await _repository.InsertAsync(item, autoSave: true);
// 每次 InsertAsync 都会立即保存到数据库
// 如果后续操作失败,已保存的数据将无法回滚
}
await uow.CompleteAsync();
}
}
适用场景:
-
允许部分成功的批量数据导入场景
-
只读操作,如查询
-
对数据一致性要求较低的场景
提交事务的方法
在事务性工作单元中
工作单元提供了几种方法来提交数据库的更改:
-
IUnitOfWork.SaveChangesAsync
await _unitOfWorkManager.Current.SaveChangesAsync();
-
仓储中的 autoSave 参数
await _repository.InsertAsync(entity, autoSave: true);
autoSave
和 SaveChangesAsync
都会在当前上下文中提交更改到数据库。然而,直到调用 CompleteAsync
时,这些更改才会生效。如果工作单元抛出异常或没有调用 CompleteAsync
,事务将会回滚。也就是说,所有数据库操作都会被撤销。只有成功执行 CompleteAsync
后,事务才会永久提交到数据库。
-
CompleteAsync 方法
using (var uow = _unitOfWorkManager.Begin())
{
// 执行数据库操作
await uow.CompleteAsync();
}
当你手动控制工作单元时,CompleteAsync
方法对事务完成至关重要。工作单元内部维护了一个 DbTransaction
对象,CompleteAsync
方法会调用 DbTransaction.CommitAsync
来提交事务。如果 CompleteAsync
没有执行或者执行失败,事务将不会被提交。
此方法不仅会提交所有数据库事务,还会:
-
执行并处理工作单元中的所有待处理领域事件
-
执行工作单元中所有已注册的后操作和清理任务
-
在处置工作单元对象时释放所有
DbTransaction
资源
注意:
CompleteAsync
方法只能调用一次,不支持多次调用。
在非事务性工作单元中
在非事务性工作单元中,这些方法的行为有所不同:
autoSave
和 SaveChangesAsync
会立即将更改持久化到数据库中,且这些更改无法回滚。即使在非事务性工作单元中,调用 CompleteAsync
方法仍然是必要的,因为它处理其他必要的任务。
示例:
using (var uow = _unitOfWorkManager.Begin(isTransactional: false))
{
// 更改会立即持久化,且无法回滚
await _repository.InsertAsync(entity1, autoSave: true);
// 此操作会独立于前一个操作持久化
await _repository.InsertAsync(entity2, autoSave: true);
await uow.CompleteAsync();
}
回滚事务的方法
在事务性工作单元中
工作单元提供了多种回滚事务的方法:
-
自动回滚
对于由 ABP 框架自动管理的事务,任何在请求过程中未捕获的异常将触发自动回滚。
-
手动回滚
对于手动管理的事务,你可以显式调用 RollbackAsync
方法来立即回滚当前事务。
重要:一旦调用了
RollbackAsync
,整个工作单元事务将会立即回滚,任何后续对CompleteAsync
的调用将没有效果。
using (var uow = _unitOfWorkManager.Begin(
isTransactional: true,
isolationLevel: IsolationLevel.RepeatableRead,
timeout: 30
))
{
await _repository.InsertAsync(entity);
if (someCondition)
{
await uow.RollbackAsync();
return;
}
await uow.CompleteAsync();
}
CompleteAsync
方法尝试提交事务。如果在此过程中发生任何异常,事务将不会提交。
以下是两种常见的异常场景:
-
在工作单元内处理异常
using (var uow = _unitOfWorkManager.Begin(
isTransactional: true,
isolationLevel: IsolationLevel.RepeatableRead,
timeout: 30
))
{
try
{
await _bookRepository.InsertAsync(book);
await uow.SaveChangesAsync();
await _productRepository.UpdateAsync(product);
await uow.CompleteAsync();
}
catch (Exception)
{
// 异常可能发生在 InsertAsync、SaveChangesAsync、UpdateAsync 或 CompleteAsync 中
// 即使某些操作成功,事务仍然未提交到数据库
// 虽然可以显式调用 RollbackAsync 来回滚事务,
// 但如果 CompleteAsync 执行失败,事务仍然不会提交
throw;
}
}
-
在工作单元外处理异常
try
{
using (var uow = _unitOfWorkManager.Begin(
isTransactional: true,
isolationLevel: IsolationLevel.RepeatableRead,
timeout: 30
))
{
await _bookRepository.InsertAsync(book);
await uow.SaveChangesAsync();
await _productRepository.UpdateAsync(product);
await uow.CompleteAsync();
}
}
catch (Exception)
{
// 异常可能发生在 UpdateAsync、SaveChangesAsync、UpdateAsync 或 CompleteAsync 中
// 即使某些操作成功,事务仍然未提交到数据库
// 由于 CompleteAsync 未成功执行,事务不会被提交
throw;
}
在非事务性工作单元中
在非事务性工作单元中,操作是不可逆的。使用 autoSave: true
或 SaveChangesAsync()
保存的更改会立即持久化,并且无法回滚。此时调用 RollbackAsync
方法没有效果。
事务管理最佳实践
1. 记得提交事务
在手动控制事务时,记得在操作完成后调用 CompleteAsync
方法提交事务。
2. 注意上下文
如果当前上下文中已经存在工作单元,UnitOfWorkManager.Begin
方法和 UnitOfWorkAttribute
会重用它。可以通过设置 requiresNew: true
来强制创建一个新的工作单元。
[UnitOfWork]
public async Task Method1()
{
using (var uow = _unitOfWorkManager.Begin(
requiresNew: true,
isTransactional: true,
isolationLevel: IsolationLevel.RepeatableRead,
timeout: 30
))
{
await Method2();
await uow.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