【问题标题】:Exception in inner TransactionScope is causing all subsequent inner TransactionScopes to throw TransactionAbortedException内部 TransactionScope 中的异常导致所有后续内部 TransactionScope 抛出 TransactionAbortedException
【发布时间】:2013-10-19 18:43:09
【问题描述】:

我有一个应用程序,我想将多个数据库保存到一个事务中。如果他们中的任何一个失败了,我想把整个事情都回滚。但是,我想在回滚事务之前知道其中哪些失败(或成功)。

我有一个带有内部循环的外部 TransactionScope,其中循环的每次迭代都有自己的 TransactionScope。我想运行所有这些并找出哪些失败了。

例如,如果我有 5 件事情我想尝试保存,但第一件和第三件都失败了,我想知道这一点。这要求我尝试所有 5 次保存,如果一次失败,则将整个东西回滚,但只有在所有 5 次都尝试过之后。

我看到的是,在第一个失败的事务之后,所有后续使用 TransactionScope 都会立即抛出他们自己的 TransactionAbortedException 并且不让我尝试保存以查看它是否有效。

这是一个例子:

using (var scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead}, EnterpriseServicesInteropOption.Full))
{
    var outputStatus = new List<string>();

    for (int i = 0 ; i < 5 ; i++)
    {
        try
        {
            using (var innerScope = new System.Transactions.TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead}, EnterpriseServicesInteropOption.Full))
            {
                // Do work here that causes an exception on first iteration only
                if (i == 0)
                {
                    throw new Exception(string.Format("Iteration {0} has FAILED", i));
                }
                else
                {
                    outputStatus.Add("SUCCESS");
                }
            }
        }
        catch (Exception e)
        {
            outputStatus.Add("ERROR, " + e.Message);
        }
    }

    // Print out outputStatus values here
}

在这段代码的最后,outputStatus 集合如下所示:

  • 错误,迭代 0 失败
  • 错误,事务已中止。
  • 错误,事务已中止。
  • 错误,事务已中止。
  • 错误,事务已中止。

在第一个异常之后,其余的都无法到达success语句。

有没有办法在外部事务范围内运行所有内部事务并允许我控制外部事务范围的回滚?

更新:

在此示例模拟的真实代码中,我无法更改包含内部 TransactionScope 的代码。它在我无法控制的物体中。因此,我正在寻找的解决方案需要能够处理内部事务抛出异常。

【问题讨论】:

    标签: c# transactionscope


    【解决方案1】:

    在尝试自己模仿后,我发现您实际上无法以这种方式或按照我最初提出的方式做到这一点。如果您无论如何都关联了事务范围,并且其中一个没有正确完成对构造函数的后续调用,则只会导致异常并中止。如果您尝试手动更改它们或嵌套它们而不关联它们,那么在完成时Dispose()。将抛出一个异常,说明您嵌套不正确或 Transaction.Current 在范围内已更改。

    在我看来,您必须在进行原子事务或独立尝试所有这些事情之间做出选择,然后检查哪个失败并在之后更正。

    最后我发现(通过使用 JetBrains dotPeek)事务具有线程关联性。您可以通过在不同的线程上执行来管理您的 5 个呼叫。当然,您将不得不使用某种屏障 http://en.wikipedia.org/wiki/Synchronous_rendezvous ,以防止任何线程完成,直到所有线程都完成。如果它们是顺序的,您将不得不使用额外的同步结构来让它们按顺序执行。

    请记住,这不会是原子的,在您决定要完成所有事务之后,它们可能仍然会出错!毕竟他们是独立的。 如果您不小心,您可能会被锁定,这取决于您的实际工作应该做什么。 此外,如果您的资源分散在不同的机器或数据库中,这可能不会很好地发挥作用,这会增加您的应用程序发出完整但远程资源决定的可能性。

    原答案:

    您应该在内部(循环)使用结束之前捕获您的异常。

    继续阅读(备注):http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx

    如果事务范围内(即在 TransactionScope 对象的初始化和调用其 Dispose 方法之间)没有发生异常,则允许该范围参与的事务进行。 如果事务范围内确实发生异常,则其参与的事务将被回滚。

    我也推荐你阅读这篇文章:http://msdn.microsoft.com/en-us/library/ms172152.aspx

    当您想要保留代码部分执行的操作并且不想在操作失败时中止环境事务时,抑制非常有用。例如,当您想要执行日志记录或审计操作时,或者当您想要向订阅者发布事件时,无论您的环境事务是提交还是中止。此值允许您在事务范围内拥有非事务性代码部分,如下例所示。

    补充:我一直在阅读 msdn,我认为如果您创建另一个级别的事务,您可能能够做到这一点。 我的理由是:

    • 您的事务失败,因为您控制的 范围(最外层)是根事务。
    • 您无法控制的代码确实要求事务但不要求新事务(参数 TransactionScopeOption.Required。)这意味着在该库 outer 内部是正在运行的事务并且失败其他一切都会失败。
    • 为防止这种情况发生,您可以在失去对外部代码的控制之前创建另一个范围。但请确保您要求一个 RequiredNew 范围。这将隔离您无法控制的代码,并让您有机会捕获该异常。

    我修改后的解决方案是这样的。

      using (var scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required))
      {
    var outputStatus = new List<string>();
    
    for (int i = 0 ; i < 5 ; i++)
    {
       //Note RequiredNew, rest of the arguments suppressed 
        using (var innerScope = new System.Transactions.TransactionScope(TransactionScopeOption.RequiredNew))
        {
            try
            {
    
                // Do work here that causes an exception on first iteration only <-- is this really the case or is just an example, if so could you skip the first one?
                SomeService.DoSOmetaskWhichUsesATransactionInsideOfIt(i);
                outputStatus.Add("SUCCESS : " + i );
                                innerScope.Complete();
    
            }
            catch (Exception e)
            {
                outputStatus.Add("ERROR, "  + i + "   " + e.Message);
            }
        }
    
    }
    // IN here you must inspect outputStatus and decide if you want to complete the transaction (all of it , or the parts that didn't fail) or not. 
    if(/* all good */) {
        scope.Complete();
    }
    // Print out outputStatus values here
    }
    

    如果这不适合您的需要,您可能需要研究更高级的事务主题并明确执行。 我建议您阅读:http://msdn.microsoft.com/en-us/library/ms172146.aspx 这超出了我对交易的理解,所以我不太确定你会如何应用它。

    【讨论】:

    • 感谢您的回复。我实际上对内部 TransactionScope 调用没有任何控制权。在实际代码(此示例模拟)中,控制内部事务范围的对象是我无权更新的对象。我已经用该信息更新了我的问题。
    • 我看到了您的更新,但是 RequiresNew 事务不会从外部事务中排除,因为它会创建一个新事务?这会让我无法回滚所有RequiredNew TransactionScopes,对吗?
    • 老实说,我不清楚它会做什么(我会尝试一下)。如果您在此站点上搜索 requiredNew 的用法,看来您是正确的,innerTransaction 是完全独立的(并且令人困惑)。如果是这种情况,您将不得不自己管理所有事务,而不是使用 using 代码块,而是使用 try-catch-finally 和静态 Transaction.Current 或 ctor TransactionScope(TransactionToUse)。请注意,在调用 Dispose 之前,不会完成或撤消任何操作。
    猜你喜欢
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多