如何在.NET Core中使用TransactionScope及解决上下文已释放错误
报错原因
- 核心触发原因是控制器调用异步方法
CreateTransaction时未添加await关键字:CreateTransaction为返回Task的异步方法,无await时控制器方法会直接执行后续逻辑并返回响应,ASP.NET Core请求生命周期结束后,依赖注入容器会自动释放注入的TransactionContext实例(默认注册为Scoped生命周期,与请求绑定),此时仍在后台运行的CreateTransaction方法访问已释放的上下文就会抛出该错误。 - 现有事务实现还存在多处逻辑问题:
- 事务操作全部完成后未调用
Commit()提交,using块结束后事务会自动回滚,所有修改都不会生效 - 账户更新逻辑错误,两次
Update调用均针对receiverAccount,转出账户senderAccount的修改不会被提交 - 金额计算逻辑存在隐性问题:
receiverAccount.Amount = senderAccount.Amount + transaction.Amount依赖sender扣减后的金额计算,后续如果修改sender的扣减逻辑容易连带出错,建议直接使用receiver原有金额累加交易金额 - 所有数据库操作均使用同步方法,在异步方法中混用同步IO容易引发线程池阻塞问题
- 事务操作全部完成后未调用
修复方案
基础修复(使用EF Core原生事务)
如果是单DbContext的事务场景,直接使用EF Core自带的事务实现更轻量,修改代码如下:
- 修复控制器调用逻辑,添加await
[HttpPost] public async Task<ActionResult<TransactionDto>> CreateNewTrasaction(TransactionDto accountCreateDto) { var commandModel = _mapper.Map<TransactionHistory>(accountCreateDto); // 等待异步事务方法执行完成 await _transactionRepository.CreateTransaction(commandModel); var accountReadDto = _mapper.Map<AccountReadDto>(commandModel); return Ok(); }
- 修复Repository的事务实现
public async Task CreateTransaction(TransactionHistory transaction) { // 使用异步开启事务 using IDbContextTransaction transactionContext = await _transContext.Database.BeginTransactionAsync(); try { transaction.CreatedDate = DateTime.UtcNow; _transContext.TransactionHistories.Add(transaction); await _transContext.SaveChangesAsync(); // 使用异步查询 var senderAccount = await _transContext.Accounts.FirstOrDefaultAsync(x => x.AccountNo == transaction.AccountNo); var receiverAccount = await _transContext.Accounts.FirstOrDefaultAsync(x => x.AccountNo == transaction.ReceiverAccount); // 前置校验,避免空引用和余额不足 if (senderAccount == null || receiverAccount == null || senderAccount.Amount < transaction.Amount) { throw new InvalidOperationException("交易参数非法,无法完成转账"); } senderAccount.Amount -= transaction.Amount; receiverAccount.Amount += transaction.Amount; // 同时更新两个账户 _transContext.Accounts.Update(senderAccount); _transContext.Accounts.Update(receiverAccount); await _transContext.SaveChangesAsync(); // 提交事务 await transactionContext.CommitAsync(); } catch (Exception ex) { // 异常时回滚事务 await transactionContext.RollbackAsync(); throw; } }
TransactionScope使用方案
如果需要跨多个DbContext或多个数据源实现分布式事务,可以使用TransactionScope,使用前先安装NuGet包System.Transactions.Local,实现代码如下:
public async Task CreateTransactionWithScope(TransactionHistory transaction) { // 必须指定TransactionScopeAsyncFlowOption.Enabled,才能支持异步上下文下的事务流转 using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); try { transaction.CreatedDate = DateTime.UtcNow; _transContext.TransactionHistories.Add(transaction); await _transContext.SaveChangesAsync(); var senderAccount = await _transContext.Accounts.FirstOrDefaultAsync(x => x.AccountNo == transaction.AccountNo); var receiverAccount = await _transContext.Accounts.FirstOrDefaultAsync(x => x.AccountNo == transaction.ReceiverAccount); if (senderAccount == null || receiverAccount == null || senderAccount.Amount < transaction.Amount) { throw new InvalidOperationException("交易参数非法,无法完成转账"); } senderAccount.Amount -= transaction.Amount; receiverAccount.Amount += transaction.Amount; _transContext.Accounts.Update(senderAccount); _transContext.Accounts.Update(receiverAccount); await _transContext.SaveChangesAsync(); // 所有操作执行成功后提交事务 scope.Complete(); } catch (Exception ex) { // 无需手动回滚,未调用Complete的scope在释放时会自动回滚所有操作 throw; } }
注意:TransactionScope依赖环境的分布式事务支持,Windows环境下依赖MSDTC服务,Linux环境下需要依赖相应的分布式事务组件,跨数据库/跨服务场景才建议使用。
内容的提问来源于stack exchange,提问作者Rooter
相关产品推荐
相关产品推荐

