如何清理调试带Rollback属性的单元测试后残留的环境事务
临时解决当前报错
你遇到的报错根源不在数据库层面,而是应用侧(测试运行进程)的.NET运行时残留了环境事务上下文、Db连接对象被标记为关联了未完成事务,所以你查数据库事务、重启数据库都没用,按以下步骤清理:
- 完全关闭当前运行的IDE(Visual Studio/Rider等),结束所有测试宿主相关的后台进程
- 删除测试项目下的
bin、obj目录,重新编译整个解决方案即可恢复正常
根本原因与永久修复
你自定义的Rollback特性存在异步兼容问题,才会导致调试时出现事务残留:
- 修复TransactionScope异步支持
默认构造的TransactionScope不支持异步上下文流转,在异步测试方法中使用时,await后事务上下文会丢失,AfterTest里的Dispose无法正确清理事务。修改Rollback类的构造逻辑:
public class Rollback : Attribute, ITestAction { private TransactionScope transaction; public void BeforeTest(ITest test) { // 新增TransactionScopeAsyncFlowOption.Enabled支持异步上下文流转 transaction = new TransactionScope( TransactionScopeOption.Required, TransactionManager.MaximumTimeout, TransactionScopeAsyncFlowOption.Enabled ); } public void AfterTest(ITest test) { transaction.Dispose(); } public ActionTargets Targets => ActionTargets.Test; }
- 修复仓储方法的潜在问题
你的AddDictionaryType方法存在同步IO、SQL注入风险,也可能触发事务上下文异常:
- 不要用字符串拼接
FromSqlRaw的SQL语句,存在SQL注入风险 FromSqlRaw返回的IQueryable直接调用同步Any()会触发同步IO,破坏异步事务上下文,改用EF Core异步LINQ查询:
public async Task AddDictionaryType(string name, string description) { try { var hasSameName = await _dbContext.DictionaryTypes.AnyAsync(d => d.Name == name); var hasSameDescription = await _dbContext.DictionaryTypes.AnyAsync(d => d.Description == description); if (hasSameName || hasSameDescription) { throw new AddingExistingDictionaryException(); } _dbContext.Add(new DictionaryType(name, description)); await _dbContext.SaveChangesAsync(); } catch (Exception ex) { throw ex; } }
- 调试注意事项
如果需要在测试断点处查询数据库表数据,不要直接查询,要么在SSMS的查询语句后加WITH (NOLOCK)读取脏数据,要么先手动释放当前事务再查询,避免锁表同时也不会触发事务残留。
内容的提问来源于stack exchange,提问作者Alter Fredo
相关产品推荐
相关产品推荐

