I have atomic writes (inserts, updates) to the database layer which will be called in a variety of ways:
Sometimes they will be called atomically, in which case the code must create a new ambient transaction.
In other situations they can be called from a parent business process in the business layer which initiates a transaction scope, where the atomic operation is one of many such database operations that must all be in the same transaction. In these cases the operation must enlist in the ambient transaction created by the outermost transaction scope using statement.
Finally, in some cases the outermost business process or outermost transaction scope using statement is creating an ambient transaction and running multiple threads to accomplish its work, and each thread does dataabse work which must be in the transaction. In these cases I need to use the DependentTransaction
pattern. (Among other scenarios, I use this pattern from unit tests where I want the entire process to leave no permanent footprints in the DB).
So, how can I (or should I) code the inner using
statments, (at all levels of the nesting), to ensure that it will function properly both when it is called from an outer transaction with a dependantTransaction, and when it is called by itself and there is no ambient transaction ??
private static readonly TransactionOptions txOptRC =
new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted };
// need this when called by itself or from non-threaded outer Tx ...
using (var scop = new TransactionScope(TransactionScopeOption.Required, txOptRC))
{
// Transactional work here
scop.Complete();
}
// need this when called from a multi-threaded or ThreadPooled outer Tx
using (var scop = new TransactionScope(dependentTransaction))
{
// Transactional work here
scop.Complete();
}
One approach I am considering is:
public void MyMethod( , , , , , DependentTransaction depTx = null)
{
using (var scop = depTx != null?
new TransactionScope(depTx):
TransactionScope(TransactionScopeOption.Required, txOptRC))
{
// Transactional Work here
scop.Complete();
}
// other stuff
}
would this cause any issues ?
Also, (second question) as I understand it, this cloned dependant transaction pattern is only necessary at the point in the nesting where you are calling subordinate (lower nested) transactional work on multiple threads, or asynchronously, (in a manner that where it is not determinisitc that the outer transactionScope code cannot finish and drop out of the closing brace of the using block before the inner nested transactions have voted...
So does this mean that if inner nestings of transactionScopes (where everything is synchronous and on a single thread), do not need to deal with the cloned dependent transaction? Can inner nested transactionScopes of this nature then simply use the standard construction ? If this is true I would only have to use the above condiitonal syntax at the point where the code creates multiple threads or calls a subordinate method asynhronously...