Neither the TransactionScope
nor SQL Server support nested transactions.
You can nest TransactionScope
instances, but that only has the outward appearance of a nested transaction. In reality, there is something called an "ambient" transaction, and there can be only one at a time. Which transaction is the ambient transaction depends on what you use for TransactionScopeOption
when you create the scope.
To explain in more detail, consider the following:
using (var outer = new TransactionScope())
{
DoOuterWork();
using (var inner1 = new TransactionScope(TransactionScopeOption.Suppress))
{
DoWork1();
inner1.Complete();
}
using (var inner2 = new TransactionScope(TransactionScopeOption.RequiresNew))
{
DoWork2();
inner2.Complete();
}
using (var inner3 = new TransactionScope(TransactionScopeOption.Required))
{
DoWork3();
inner3.Complete();
}
outer.Complete();
}
Here is what happens for each of the inner scopes:
inner1
is executed in an implicit transaction, independently of outer
. Nothing that happens in DoWork1
is guaranteed to be atomic. If this fails midway through, you'll have inconsistent data. Any work that happens in here is always committed, regardless of what happens to outer
.
inner2
is executed in a new transaction, independently of outer
. This is a different transaction from outer
but it is not nested. If it fails, the work that happened in outer
(DoOuterWork()
) and any of the other scopes can still be committed, but here's the rub: If it completes, then rolling back the entire outer
transaction will not roll back the work done inside inner2
. This is why it is not truly nested. Also, inner2
won't have access to any rows locked by outer
, so you could end up with deadlocks here if you're not careful.
inner3
is executed in the same transaction as outer
. This is the default behaviour. If DoWork3()
fails and inner3
never completes, then the entire outer
transaction is rolled back. Similarly, if inner3
completes successfully but outer
is rolled back, then any work done in DoWork3()
is also rolled back.
So you can hopefully see that none of these options are actually nested, and won't give you what you want. The Required
option approximates a nested transaction, but doesn't give you the ability to independently commit or roll back specific units of work inside the transaction.
The closest thing you can get to true nested transactions in SQL Server is the SAVE TRAN
statement combined with some TRY/CATCH
blocks. If you can put your logic inside one or more Stored Procedures, this would be a good option.
Otherwise, you'll need to use separate transactions for each invoice as per Oded's suggestion.