【问题标题】:Using transactions across processes跨进程使用事务
【发布时间】:2026-01-14 04:40:01
【问题描述】:

我正在尝试使用 System.Transactions (TransactionScope) 来协调一组进程,每个进程都执行一些数据库工作。最终,所有进程都需要通过一个父进程自动提交或回滚。不幸的是,到目前为止我没有尝试过任何工作。

我的基本策略是在父进程中TransactionScope,将其保存到文件中,然后调用子进程,该子进程加载文件,在自己的TransactionScope中使用事务,然后返回给父进程。

但这对我不起作用。当我打电话给第一个孩子回来时,我有时会看到父事务已被标记为已中止。尝试克隆它然后抛出 TransactionAbortedException。

当第二个孩子尝试反序列化事务时,我也看到了异常,我得到了一个代码为 0x8004d00e 的 TransactionException。

我正在尝试执行 TransactionScope across AppDomains and processeshttp://blogs.microsoft.co.il/blogs/sasha/archive/2010/04/30/propagating-a-transaction-across-appdomains.aspx 中描述的操作。无论如何,没有运气。

以下是我尝试过的一些事情,但没有成功:

  1. 从加载的事务中通过 DependentClone() 在子进程中创建一个 DependentTransaction
  2. 在保存之前在父进程中通过 DependentClone() 创建一个 DependentTransaction 交易到文件
  3. 在父进程中创建一个 Clone(),然后再保存 交易到文件
  4. 使用序列化保存事务
  5. 保存交易使用 TransactionInterop.GetTransactionFromTransmitterPropagationToken()
  6. 在父节点之前显式打开连接 交易范围
  7. 在父级中显式登记事务
  8. 在子进程中显式征用事务
  9. 完成/未完成父级范围
  10. 在孩子身上完成/不完成范围
  11. 在父级中显式创建 CommittableTransaction

这是一条异常消息:

System.Transactions.TransactionException:事务已被隐式或显式提交或中止。 ---> System.Runtime.InteropServices.COMException:事务已被隐式或显式提交或中止(来自 HRESULT 的异常:0x8004D00E)

还有另一个(使用 DependentClone() 时):

System.Transactions.TransactionAbortedException:事务已中止。 在 System.Transactions.TransactionStatePromotedAborted.CreateBlockingClone(在 ternalTransaction tx) 在 System.Transactions.DependentTransaction..ctor(IsolationLevel isoLevel,在 ternalTransaction internalTransaction,布尔阻塞) 在 System.Transactions.Transaction.DependentClone(DependentCloneOption cloneO 选项)

任何想法我做错了什么?我已经尝试了很多这种排列,但没有任何运气。

这里是一些代码(并没有试图展示上述所有变体):

        // one variant I have tried is to create a CommittableTransaction
        // and pass that in the scope below

        using (TransactionScope scope = new TransactionScope())
        {
            // optionally, do some parent-level EF work

            // invoke child operations in other processes
            DoChildOperation_OutOfProc(1, Transaction.Current);
            DoChildOperation_OutOfProc(2, Transaction.Current);

            scope.Complete();
        }

        // in the variant where I created a CommittableTransaction,
        // I committed it here

    ...

    private static void DoChildOperation_OutOfProc(int id, Transaction transaction)
    {
        string tranFile = string.Format("ChildTran_{0}.txt", id);
        SaveTransactionToFile(transaction, tranFile);

        Process process = new Process();
        process.StartInfo = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName.Replace(".vshost", string.Empty),
            string.Format("-CHILDID={0} -TRANFILE={1}", id, tranFile));
        process.StartInfo.UseShellExecute = false;
        process.Start();
        process.WaitForExit();
    }

    private static void SaveTransactionToFile(Transaction transaction, string tranFile)
    {
        byte[] transactionBytes =
            TransactionInterop.GetTransmitterPropagationToken(transaction);

        string tranFileContents = Convert.ToBase64String(transactionBytes);

        File.WriteAllText(tranFile, tranFileContents);
    }

    private static Transaction LoadTransactionFromFile(string tranFile)
    {
        string tranFileContents = File.ReadAllText(tranFile);
        File.Delete(tranFile);

        byte[] tranBytes = Convert.FromBase64String(tranFileContents);

        Transaction tran = 
            TransactionInterop.GetTransactionFromTransmitterPropagationToken(tranBytes);
        return tran;
    }

    // the child instance of the app runs this after decoding the arguments
    // from DoChildOperation_OutOfProc() and loading the transaction out of the file

    private static void DoChildOperation(int id, Transaction childTransaction)
    {
        // in one variant, I call 
        // childTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete)
        // and then use that inside the TransactionScope

        using (TransactionScope scope = new TransactionScope(childTransaction))
        {
            // do EF work and call SaveChanges()

            scope.Complete();
        }

        // if I created a dependent clone, call Complete() here on it

【问题讨论】:

  • 很高兴您已经解释了您遇到的问题/错误..但是您能否显示正在执行事务代码的完整代码块..?
  • 我添加了一些代码,经过了一些处理,但希望它更清楚。
  • 这看起来有点毛茸茸的。任何不能将工作拆分为多个事务然后在必要时使用补偿回滚的原因>
  • 最终目标是能够调用其他服务来分发工作。每个服务只应根据提供给它的信息(可能包括也可能不包括交易)了解其工作。如果数据被部分提交,那么数据库将处于损坏状态,即使我可以实现补偿逻辑。此外,补偿逻辑可能会增加额外的状态,并且肯定会增加很多额外的复杂性。我引用的链接中的信息表明您可以像我所做的那样传递交易,所以令人困惑的是这不仅对我有用。
  • 我遇到了类似的问题,但这是因为我正在卸载应用程序域。在提交父事务之前,我必须保持应用程序域处于活动状态,然后我卸载了应用程序域。 --> social.msdn.microsoft.com/Forums/en-US/…

标签: c# .net transactionscope msdtc system.transactions


【解决方案1】:

好的,关键似乎是您可以在父级中使用 TransactionScope,但不能在子级中使用。对于孩子,我打开 EF 连接,使用传递的事务调用 connection.EnlistTransaction(),然后执行 EF SaveChanges() 或 transaction.Rollback() 但不提交(Transaction 类不提供此功能)。这样做,似乎我得到了想要的行为。

我理解的差距实际上是事务是否嵌套(就像您在 SQL Server 中所做的那样)。看来确实不是;这是同一笔交易。注意:即使你在 child 中使用 Transaction.DependentClone() 创建了一个 DependentTransaction,如果你把它放到一个 TransactionScope 中,你仍然会失败。

这证明有点不幸,因为这意味着如果你的进程是父进程,你可以使用TransactionScope,但如果是子进程,你就不能。

【讨论】: