我有一个应用程序,我想将几个数据库保存放入一个事务中。如果他们中的任何一个失败了,我想把整件事推回去。但是,我想知道在回滚事务之前哪些失败(或成功)。
我有一个带有内部循环的外部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集合如下所示:
在第一个异常之后,其余的都没有能够到达成功语句。
有没有办法在外部事务范围内运行所有内部事务并允许我控制外部事务范围的回滚?
更新:
在此示例模拟的实际代码中,我无法更改包含内部TransactionScope的代码。它在一个不受我控制的物体中。因此,我正在寻找的解决方案需要能够处理内部事务抛出异常。
答案 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上阅读,如果你创建了另一个级别的交易,我认为你可能会这样做。 我的理由是:
我修改后的解决方案就像是。
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 这超出了我对交易的理解,所以我不太清楚你将如何应用它。