You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

集成测试多线程模式下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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 00:05:13