I have the following scenario:
Stack Trace:
at System.Transactions.TransactionStatePromotedAborted.BeginCommit(InternalTransaction tx, Boolean asyncCommit, AsyncCallback asyncCallback, Object asyncState)
at System.Transactions.CommittableTransaction.Commit()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
at DistributedTransactions.Program.Main() in c:\Users\agolan.ALLSHARE\Documents\Visual Studio 2013\Projects\DistributedTransactions\DistributedTransactions\Program.cs:line 44
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Here is the code of the application:
internal class Program {
private static void Main() {
string connString = "data source=.;initial catalog=Test;integrated security=True;persist security info=True";
string tokenFile = @"c:\Temp\token.txt";
Transaction transaction = null;
bool isChild = false;
if (File.Exists(tokenFile)) {
isChild = true;
string tokenString = File.ReadAllText(tokenFile);
byte[] token = Convert.FromBase64String(tokenString);
transaction = TransactionInterop.GetTransactionFromTransmitterPropagationToken(token);
}
using (var transactionScope = transaction != null ? new TransactionScope(transaction) : new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 15, 0))) {
var curr = Transaction.Current;
if (!isChild) {
byte[] transactionBytes = TransactionInterop.GetTransmitterPropagationToken(curr);
string tokenString = Convert.ToBase64String(transactionBytes);
File.WriteAllText(tokenFile, tokenString);
}
using (var conn = new SqlConnection(connString)) {
conn.Open();
using (SqlCommand cmd = conn.CreateCommand()) {
Console.WriteLine("Enter id and value");
cmd.CommandText = "INSERT INTO KeyValue(Id, Value) VALUES (@1, @2)";
cmd.Parameters.Add(new SqlParameter("@1", Console.ReadLine()));
cmd.Parameters.Add(new SqlParameter("@2", Console.ReadLine()));
cmd.ExecuteNonQuery();
}
}
transactionScope.Complete();
Console.WriteLine("Dispose");
Console.ReadLine();
}
}
}
The questions:
I was able to replicate this locally and it happens because the other application has exited before the parent process has committed the transaction. The other applications must continue running until the parent commits or rolls back the transaction. One way of doing this is preventing the applications from exiting until the TransactionCompleted event is fired.
I revised your code to use a ManualResetEventSlim to make the child process wait for the parent transaction process to complete before exiting.
internal class Program
{
private static void Main()
{
string connString = "data source=.;initial catalog=Test;integrated security=True;persist security info=True";
string tokenFile = @"c:\Temp\token.txt";
Transaction transaction = null;
bool isChild = false;
if (File.Exists(tokenFile))
{
isChild = true;
string tokenString = File.ReadAllText(tokenFile);
byte[] token = Convert.FromBase64String(tokenString);
transaction = TransactionInterop.GetTransactionFromTransmitterPropagationToken(token);
}
using (var parentTxCompleteEvent = new ManualResetEventSlim(!isChild))
{
using (var transactionScope = transaction != null ? new TransactionScope(transaction) : new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 15, 0)))
{
var curr = Transaction.Current;
if (!isChild)
{
byte[] transactionBytes = TransactionInterop.GetTransmitterPropagationToken(curr);
string tokenString = Convert.ToBase64String(transactionBytes);
File.WriteAllText(tokenFile, tokenString);
}
else
{
transaction.TransactionCompleted += (sender, e) => parentTxCompleteEvent.Set();
}
using (var conn = new SqlConnection(connString))
{
conn.Open();
using (SqlCommand cmd = conn.CreateCommand())
{
Console.WriteLine("Enter id and value");
cmd.CommandText = "INSERT INTO KeyValue(Id, Value) VALUES (@1, @2)";
cmd.Parameters.Add(new SqlParameter("@1", Console.ReadLine()));
cmd.Parameters.Add(new SqlParameter("@2", Console.ReadLine()));
cmd.ExecuteNonQuery();
}
}
transactionScope.Complete();
Console.WriteLine("Dispose");
Console.ReadLine();
}
parentTxCompleteEvent.Wait();
}
}
}