1
votes

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...

1

1 Answers

0
votes

I would have two overloads of same method once gets the outer transaction as parameter and the other one gets nothing.

your two blocks from the question's body would become those two overloads, I would then put all the common code as you have just called it:

// Transactional work here

into a private method of the class so to do not write it twice.