0
votes

As far as I know, The MSDTC only gets involved when:

  • You're querying a view/table inside a transaction that is linked to another server.

  • You're using two SqlConnections (or whatever it is NHibernate uses) within a single TransactionScope

  • You're enlisting another transactional component (like MSMQ or the transactional file system) inside a TransactionScope.

  • Other situations not mentioned.

If I disable MSDTC and run the following code, I get (MSDTC on server 'Server' is unavailable) an error.

public bool Add(PurchaseOrderInfo purchaseOrderInfo)
{
    using (TransactionScope ts = new TransactionScope())
    {
        using (SqlConnection Cnn = new SqlConnection(SqlHelper.ConnStr))
        {
            Cnn.Open();

                try
                {
                    using (SqlDataReader rdr = SqlHelper.ExecuteReader(/*Tr*/Cnn, "spPurchaseOrderAdd", purchaseOrderInfo.ExpectedShipment.ShipmentID, purchaseOrderInfo.CreateDate, purchaseOrderInfo.CustomerNotes, purchaseOrderInfo.Status, purchaseOrderInfo.PurchaseOrderNumber))
                    {
                        if (rdr.Read())
                            FillPurchaseOrderInfo(rdr, ref purchaseOrderInfo, GettingDepthEnum.Level_0);
                        else
                        {
                            return false;
                        }
                    }

                    foreach (PurchaseOrderDetailInfo detailInfo in purchaseOrderInfo.Details)
                    {
                        throw new Exception("Test");
                        //if (!AddPurchaseOrderDetail(Tr, purchaseOrderInfo, detailInfo))
                        {
                            //Tr.Rollback();
                            return false;
                        }
                    }

                    return true;
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                ts.Complete();
            }
        }
    }

Am I missing anything?

Update: The stored procedure contains a simple insert statement:

INSERT INTO tblPurchaseOrder
(ShipmentID, CreateDate, CustomerNotes, PurchaseOrderState, PurchaseOrderNumber, LastActivityDate)
VALUES
(@ShipmentID, @CreateDate, @CustomerNotes, @PurchaseOrderState, @PurchaseOrderNumber, GETDATE());

--Step 2: return row that INSERTED to Client Computer.
SELECT dbo.viwGetPurchaseOrderWeight.Weight,* FROM tblPurchaseOrder LEFT OUTER JOIN viwGetPurchaseOrderWeight ON viwGetPurchaseOrderWeight.PurchaseOrderID =  tblPurchaseOrder.PurchaseOrderID WHERE (tblPurchaseOrder.PurchaseOrderID = Scope_Identity());

UPDATE2: When shutting down the MSDTC, the exception is thrown when the execution reached to this line:

using (SqlDataReader rdr = SqlHelper.ExecuteReader(/*Tr*/Cnn, "spPurchaseOrderAdd", purchaseOrderInfo.ExpectedShipment.ShipmentID, purchaseOrderInfo.CreateDate, purchaseOrderInfo.CustomerNotes, purchaseOrderInfo.Status, purchaseOrderInfo.PurchaseOrderNumber))

which means the subsequent lines has no effect.

4
What are your procs doing? Do they involve linked servers?David Hoerster
@DavidHoerster I updated the question to include the procedure.Nour
Do you have a second db connection being used? It doesn't appear to be that, but I believe that if a second connection is introduced within the TransactionScope, then the transaction gets promoted to the DTC. Otherwise it should stay local.David Hoerster
@DavidHoerster that is exactly what confuses me!! there is no other connection! SqlHelper is a class from Microsoft.ApplicationBlocks.Data assembly, and I don't think it is opening a connection internally !!Nour
It is easier getting answers if you post a stripped down code sample. Try to get rid of all the debug cruft.usr

4 Answers

2
votes

Just be sure not open again the connection,

using (TransactionScope transactionScope = new TransactionScope()) {
    using (SqlConnection connection = new SqlConnection(connectionString)) {
    connection.Open();
    connection.Close();
    connection.Open(); // escalates to DTC
      }
}
1
votes

According to your post with the stack trace the EntLib class is opening another connection. We need to exclude it from the transaction. Wrap your call to ExecuteReader in a suppressing transaction scope:

SqlDataReader rdr;
using (var tsSuppress = new TransactionScope(TransactionScopeOption.Suppress))
    rdr = SqlHelper.ExecuteReader(...)

This will momentarily set Transaction.Current to null so that the new connection does not pick up the transaction.

As a side note, the stack trace allowed us to find the root cause of the problem.

0
votes

After checking the stack trace, it appeared that the SQLHelper class is opening a connection to get the list of required parameters for stored procedure from database:

   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
   at System.Data.SqlClient.SqlDataReader.get_MetaData()
   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.TdsParser.GetDTCAddress(Int32 timeout, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlInternalConnectionTds.GetDTCAddress()
   at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
   at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
   at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
   at System.Data.SqlClient.SqlConnection.Open()
   at Microsoft.ApplicationBlocks.Data.SqlHelperParameterCache.DiscoverSpParameterSet(SqlConnection connection, String spName, Boolean includeReturnValueParameter)
   at Microsoft.ApplicationBlocks.Data.SqlHelperParameterCache.GetSpParameterSetInternal(SqlConnection connection, String spName, Boolean includeReturnValueParameter)
   at Microsoft.ApplicationBlocks.Data.SqlHelperParameterCache.GetSpParameterSet(SqlConnection connection, String spName, Boolean includeReturnValueParameter)
   at Microsoft.ApplicationBlocks.Data.SqlHelperParameterCache.GetSpParameterSet(SqlConnection connection, String spName)
   at Microsoft.ApplicationBlocks.Data.SqlHelper.ExecuteReader(SqlConnection connection, String spName, Object[] parameterValues)
   at AlefTextileProduction.SQLServerDAL.PurchaseOrder.Add(PurchaseOrderInfo purchaseOrderInfo) 

I'm trying to find a solution around this !!

0
votes

In looking through the code of SQLHelper it looks like if you use the overload of ExecuteReader that takes SqlParameter instances instead of just the parameter values, then you'll save the step where SQLHelper tries to figure out what parameter maps to what value - and thereby saving a second trip to the database and the second connection and the promotion of your transaction to the MSDTC.

According to the code I'm looking at, this is the method you probably should call:

Public Overloads Shared Function ExecuteReader(ByVal connectionString As String, _
                 ByVal commandType As CommandType, _
                 ByVal commandText As String, _
                 ByVal ParamArray commandParameters() As SqlParameter) As SqlDataReader

If that doesn't work, then I'd suggest you roll your own DAL. :)

Hope this helps.