内部TransactionScope中的异常导致所有后续内部TransactionScopes抛出TransactionAbortedException

时间:2013-10-11 15:52:08

标签: c# transactionscope

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

我有一个带有内部循环的外部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已失败
  • 错误,交易已中止。
  • 错误,交易已中止。
  • 错误,交易已中止。
  • 错误,交易已中止。

在第一个异常之后,其余的都没有能够到达成功语句。

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

更新:

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

1 个答案:

答案 0 :(得分:2)

在尝试模仿这个之后我发现你实际上不能用这种方式或者我最初提出的方式来做。如果您仍然关联事务范围并且其中一个未正确完成,则后续对构造函数的调用将导致异常并中止。如果您尝试手动更改它们或嵌套它们而不关联它们,那么在完成后 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上阅读,如果你创建了另一个级别的交易,我认为你可能会这样做。 我的理由是:

  • 您的交易失败,因为您控制的范围(最外层)是根交易。
  • 您不能控制的代码确实要求交易而不是新的交易(Argument TransactionScopeOption.Required。)这意味着该库外部内部是正在进行的交易和失败其他的东西都会失败。
  • 为了防止这种情况发生,您可以在失去对外部代码的控制权之前创建另一个范围。但请确保您要求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 这超出了我对交易的理解,所以我不太清楚你将如何应用它。