I'm trying to use System.Transactions (TransactionScope) to coordinate a set of processes, each of which does some database work. Ultimately all processes need to commit or be rolled back atomically via one parent process. Unfortunately, nothing I've tried so far works.
My basic strategy is to TransactionScope in the parent process, save it to a file, and invoke a child process, which loads the file, uses the transaction inside its own TransactionScope, and returns to the parent.
But this doesn't work for me. When I get back from calling the first child, I am sometimes seeing that the parent transaction has been marked as Aborted. Attempting to clone it then throws TransactionAbortedException.
I have also seen exceptions when the second child attempts to deserialize the Transaction, I get a TransactionException with code 0x8004d00e.
I am trying to do what is described in TransactionScope across AppDomains and processes , and http://blogs.microsoft.co.il/blogs/sasha/archive/2010/04/30/propagating-a-transaction-across-appdomains.aspx . Regardless, no luck.
Here are some things I have tried, without success:
- Creating a DependentTransaction via DependentClone() in the child process from the loaded transaction
- Creating a DependentTransaction via DependentClone() in the parent process, before saving the transaction to a file
- Creating a Clone() in the parent process, before saving the transaction to a file
- Saving the transaction using serialization
- Saving the transaction using TransactionInterop.GetTransactionFromTransmitterPropagationToken()
- Explicitly opening the connection before the parent's TransactionScope
- Explicitly enlisting the transaction inside the parent
- Explicitly enlisting the transaction inside the child
- Completing/not completing the scope on the parent
- Completing/not completing the scope on the child
- Explicitly creating a CommittableTransaction in the parent
Here's one exception message:
System.Transactions.TransactionException: The transaction has already been implicitly or explicitly committed or aborted. ---> System.Runtime.InteropServices.COMException: The transaction has already been implicitly or explicitly committed or aborted (Exception from HRESULT: 0x8004D00E)
And the other (when using DependentClone()):
System.Transactions.TransactionAbortedException: The transaction has aborted. at System.Transactions.TransactionStatePromotedAborted.CreateBlockingClone(In ternalTransaction tx) at System.Transactions.DependentTransaction..ctor(IsolationLevel isoLevel, In ternalTransaction internalTransaction, Boolean blocking) at System.Transactions.Transaction.DependentClone(DependentCloneOption cloneO ption)
Any ideas what I'm doing wrong? I've tried a lot of permutations of this without any luck.
Here is some code (which doesn't attempt to demonstrate all the variants described above):
// one variant I have tried is to create a CommittableTransaction
// and pass that in the scope below
using (TransactionScope scope = new TransactionScope())
{
// optionally, do some parent-level EF work
// invoke child operations in other processes
DoChildOperation_OutOfProc(1, Transaction.Current);
DoChildOperation_OutOfProc(2, Transaction.Current);
scope.Complete();
}
// in the variant where I created a CommittableTransaction,
// I committed it here
...
private static void DoChildOperation_OutOfProc(int id, Transaction transaction)
{
string tranFile = string.Format("ChildTran_{0}.txt", id);
SaveTransactionToFile(transaction, tranFile);
Process process = new Process();
process.StartInfo = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName.Replace(".vshost", string.Empty),
string.Format("-CHILDID={0} -TRANFILE={1}", id, tranFile));
process.StartInfo.UseShellExecute = false;
process.Start();
process.WaitForExit();
}
private static void SaveTransactionToFile(Transaction transaction, string tranFile)
{
byte[] transactionBytes =
TransactionInterop.GetTransmitterPropagationToken(transaction);
string tranFileContents = Convert.ToBase64String(transactionBytes);
File.WriteAllText(tranFile, tranFileContents);
}
private static Transaction LoadTransactionFromFile(string tranFile)
{
string tranFileContents = File.ReadAllText(tranFile);
File.Delete(tranFile);
byte[] tranBytes = Convert.FromBase64String(tranFileContents);
Transaction tran =
TransactionInterop.GetTransactionFromTransmitterPropagationToken(tranBytes);
return tran;
}
// the child instance of the app runs this after decoding the arguments
// from DoChildOperation_OutOfProc() and loading the transaction out of the file
private static void DoChildOperation(int id, Transaction childTransaction)
{
// in one variant, I call
// childTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete)
// and then use that inside the TransactionScope
using (TransactionScope scope = new TransactionScope(childTransaction))
{
// do EF work and call SaveChanges()
scope.Complete();
}
// if I created a dependent clone, call Complete() here on it