3

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

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

例如,如果我有 5 件东西我想尝试保存,但第 1 件和第 3 件都失败了,我想知道这一点。这要求我尝试所有 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 已失败
  • 错误,事务已中止。
  • 错误,事务已中止。
  • 错误,事务已中止。
  • 错误,事务已中止。

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

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

更新:

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

4

1 回答 1

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 这超出了我对事务的理解,所以我不太确定您将如何应用它。

于 2013-10-11T17:51:24.510 回答