理解 ABP 工作单元中的事务

目录

事务管理概述 

事务行为 

默认事务设置

自动事务管理

HTTP 请求的事务行为

手动事务控制

使用 [UnitOfWork] 特性配置事务

非事务性工作单元

提交事务的方法

在事务性工作单元中

在非事务性工作单元中

回滚事务的方法

在事务性工作单元中

在非事务性工作单元中

事务管理最佳实践 

1. 记得提交事务

2. 注意上下文

3. 使用 virtual 方法

4. 避免长时间事务

事务相关建议 

参考资料 


 

工作单元(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

引入地址 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值