内部事务范围中的异常导致所有后续内部事务范围引发事务中止异常
本文关键字:内部事务 异常 范围 事务 | 更新日期: 2023-09-27 18:34:52
我有一个应用程序,我想将多个数据库保存放入一个事务中。 如果其中任何一个失败了,我想把整个事情回滚回来。 但是,我想知道在回滚事务之前哪些失败(或成功(。
我有一个带有内部循环的外部事务范围,其中循环的每个迭代都有自己的事务范围。 我想运行所有这些并找出哪些失败了。
例如,如果我有 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
}
在此代码的末尾,输出状态集合如下所示:
- 错误,迭代 0 失败
- 错误,事务已中止。
- 错误,事务已中止。
- 错误,事务已中止。
- 错误,事务已中止。
在第一个异常之后,其余的都无法访问 success 语句。
有没有办法运行外部事务范围内的所有内部事务并允许我控制外部事务范围的回滚?
更新:
在此示例模拟的实际代码中,我无法更改包含内部 TransactionScope 的代码。 它在一个我无法控制的对象中。 因此,我正在寻找的解决方案需要能够处理引发异常的内部事务。
在尝试自己模仿之后,我发现您实际上无法以这种方式或我最初提出的建议来做到这一点。如果您无论如何都关联事务范围,并且其中一个未正确完成,则对构造函数的后续调用只会导致异常并中止。如果您尝试手动更改它们或嵌套它们而不关联它们,则在完成后 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.Required.(,这意味着该库外部是正在运行的事务,并且失败了其他所有事情都会失败。
- 为了防止这种情况发生,您可以在失去对外部代码的控制之前创建另一个作用域。 但请确保要求"必需的新"范围。这将隔离您无法控制的代码,并让您有机会捕获该异常。
我修改后的解决方案将是这样的。
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这超出了我对交易的理解,所以我不太确定你会如何应用它。