We're trying to run parallel queries inside a transaction scope to improve performance of our code. We have several changes to be made in the database that have no connection to each other. We could run the code like this:
using(var tran = new System.Transactions.TransactionScope())
{
await queryMethod1Async();
await queryMethod2Async();
await queryMethod3Async();
tran.Complete();
}
however, since the methods are independent of each other, we would like to run the code like this:
using(var tran = new System.Transactions.TransactionScope())
{
var tasks = new List<Task>();
tasks.Add(queryMethod1Async());
tasks.Add(queryMethod2Async());
tasks.Add(queryMethod3Async());
await Task.WhenAll(tasks);
tran.Complete();
}
We are running into some issues with the parallel execution:
- Running the queries in parallel seems to escalate the transaction. During escalation, sometimes an error occurs:
The wait operation timed out --> There is already an open DataReader associated with this Command which must be closed first.
at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest)
at System.Data.SqlClient.SqlDelegatedTransaction.Promote() --> Failure while attempting to promote transaction.
at System.Data.SqlClient.SqlDelegatedTransaction.Promote()
at System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx)
at System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx) --> The transaction has aborted.
at System.Transactions.TransactionStateAborted.CheckForFinishedTransaction(InternalTransaction tx)
at System.Transactions.Transaction.Promote()
at System.Transactions.TransactionInterop.ConvertToOletxTransaction(Transaction transaction)
at System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts)
at System.Data.SqlClient.SqlInternalConnection.GetTransactionCookie(Transaction transaction, Byte[] whereAbouts)
at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
at System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction)
After some investigation, this seems to be because during escalation, the original connection for the transaction is used, but during parallel execution, this connection might be in use. I've tried to enable MARS to avoid this issue, but this results in a different error:
Current Microsoft Distributed Transaction Coordinator (MS DTC) transaction must be committed by remote client.
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest)
at System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment) --> The transaction has aborted.
To clarify, each of the above methods create a new sqlconnection, and we have configured MSDTC correctly
I'm not sure why this second error occurs, but I have a feeling I'm going about this the wrong way. Is it possible to do parallel query execution inside a transaction scope, and if so, what is the right way to go about this?