集成测试多线程模式下Transaction回滚失效问题
问题描述
我在集成测试中采用“每个测试在事务中执行并最终回滚”的方案,定义了如下基类,所有集成测试类都继承它:
public abstract class IntegrationTestBase { private TransactionScope scope; [TestInitialize] public void TestInitialize() { scope = new TransactionScope(); } [TestCleanup] public void TestCleanup() { scope.Dispose(); } }
这个方案正常情况下可以工作,但在并行执行的测试代码中失效:
[TestMethod] public void SaveTwoDocumentsSimultaneously_WorkSuccessfully() { //Assign var doc1 = new Document() { Number = "Test-1" }; var doc2 = new Document() { Number = "Test-2" }; //Action Parallel.Invoke(() => SaveNewDocument(doc1), () => SaveNewDocument(doc2)); //Assert Assert.IsTrue(true); } void SaveNewDocument(Document doc) { using (var ctx = new MyDbContext()) { ctx.Documents.Add(doc); ctx.SaveChanges(); } }
测试通过后,数据库中仍保留了编号为Test-2的记录,事务没有按预期回滚(预期两条记录都不应该存在)。根据DependentTransaction的相关文档,我认为需要使用它来处理并行场景,请问如何将IntegrationTestBase中开启的TransactionScope作为父事务供并行任务使用?
解决方案
要让并行任务共享父事务并确保最终回滚,需要通过DependentTransaction将父事务上下文传递给每个并行线程,具体修改如下:
1. 修改IntegrationTestBase,增加依赖事务辅助方法
public abstract class IntegrationTestBase { private TransactionScope scope; private Transaction parentTransaction; [TestInitialize] public void TestInitialize() { scope = new TransactionScope(); // 获取当前父事务实例 parentTransaction = Transaction.Current; } [TestCleanup] public void TestCleanup() { scope.Dispose(); } // 为并行任务创建依赖事务范围的方法 protected TransactionScope CreateDependentTransactionScope() { if (parentTransaction == null) { throw new InvalidOperationException("父事务未初始化"); } // 创建依赖事务,阻塞子事务提交直到父事务完成 var dependentTx = parentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete); return new TransactionScope(dependentTx); } }
2. 修改测试方法,在并行任务中使用依赖事务
[TestMethod] public void SaveTwoDocumentsSimultaneously_WorkSuccessfully() { //Assign var doc1 = new Document() { Number = "Test-1" }; var doc2 = new Document() { Number = "Test-2" }; //Action Parallel.Invoke( () => SaveNewDocumentWithDependentTransaction(doc1), () => SaveNewDocumentWithDependentTransaction(doc2) ); //Assert Assert.IsTrue(true); } void SaveNewDocumentWithDependentTransaction(Document doc) { // 基于父事务创建依赖事务范围 using (var dependentScope = CreateDependentTransactionScope()) using (var ctx = new MyDbContext()) { ctx.Documents.Add(doc); ctx.SaveChanges(); // 标记子任务操作完成,最终提交由父事务决定 dependentScope.Complete(); } }
关键说明
DependentCloneOption.BlockCommitUntilComplete:确保父事务完成(回滚或提交)前,所有依赖事务的提交请求都会被阻塞,避免子事务提前提交导致数据残留。- 每个并行任务必须使用独立的
TransactionScope(基于父事务的依赖事务),这样才能将操作纳入父事务上下文,最终随父事务一起回滚。 - 依赖事务的
Complete()仅标记当前子任务操作完成,最终是否提交由父事务决定(父事务在TestCleanup中Dispose时会触发回滚)。
内容的提问来源于Stack Exchange,提问作者Masoud
相关产品推荐
相关产品推荐

