跨流程使用事务

时间:2012-11-26 22:28:07

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

我正在尝试使用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. 通过已加载的事务
  2. 在子进程中通过DependentClone()创建DependentTransaction
  3. 在保存之前,通过父进程中的DependentClone()创建DependentTransaction 交易到文件
  4. 在保存之前,在父进程中创建一个Clone() 交易到文件
  5. 使用序列化保存交易
  6. 使用保存交易 TransactionInterop.GetTransactionFromTransmitterPropagationToken()
  7. 在父母之前明确打开连接 的TransactionScope
  8. 明确地在父母
  9. 中征集交易
  10. 明确地在儿童内部招募交易
  11. 完成/未完成父母的范围
  12. 完成/未完成孩子的范围
  13. 在父
  14. 中明确创建CommittableTransaction

    这是一条例外消息:

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

    另一个(当使用DependentClone()时):

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

    任何想法我做错了什么?我没有运气就尝试了很多这样的排列。

    以下是一些代码(不会尝试演示上述所有变体):

            // 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
    

1 个答案:

答案 0 :(得分:1)

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

我理解的差距实际上是交易是否嵌套(在SQL Server中可以做到)。看来它确实不是;这是同一笔交易。注意:即使您在子级中使用Transaction.DependentClone()创建DependentTransaction,如果将其放入TransactionScope中,仍会失败。

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