Transaction promoted on simple database inserts

Transaction promoted on simple database inserts

Old forum URL: forums.lhotka.net/forums/t/11562.aspx


j0552 posted on Tuesday, September 04, 2012

Hi

 

I have an IIS application (windows 2003, .net 4, CSLA 4) with a simple root BB object with a child collection with the transactional attribute added to the root DataPortal_Update method:

 

        [Transactional(TransactionalTypes.TransactionScope)]

        protected override void DataPortal_Update()

        {

            FieldManager.UpdateChildren();

        }

 

The child insert/delete methods call  code like (simplified for illustration):

 

        public void Insert(Dto data)

        {

            using (var ctx = ConnectionManager<SqlConnection>.GetManager(Database.Name))

            {

                var command = ctx.Connection.CreateCommand();

                command.CommandType = CommandType.StoredProcedure;

                command.CommandText = "sp_Insert";

                command.Parameters.AddWithValue("@ID", data.ID);

                command.ExecuteNonQuery();

            }

        }

 

The database connection is to a single SQL Server 2005 database which is located on a remote machine. The connection uses a SQL login rather than a trusted one.

 

My question is why does the transaction get promoted to Oletx? Please see exception details:

 

- DataPortal.Update failed (Csla.DataPortalException: ChildDataPortal.Update failed on the server ---> Csla.Reflection.CallMethodException: Child_Update method call failed ---> Csla.DataPortalException: ChildDataPortal.Update failed on the server ---> Csla.Reflection.CallMethodException: Child_Insert method call failed ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Transactions.TransactionException: The partner transaction manager has disabled its support for remote/network transactions. (Exception from HRESULT: 0x8004D025) ---> System.Runtime.InteropServices.COMException: The partner transaction manager has disabled its support for remote/network transactions. (Exception from HRESULT: 0x8004D025)

   at System.Transactions.Oletx.IDtcProxyShimFactory.ReceiveTransaction(UInt32 propgationTokenSize, Byte[] propgationToken, IntPtr managedIdentifier, Guid& transactionIdentifier, OletxTransactionIsolationLevel& isolationLevel, ITransactionShim& transactionShim)

   at System.Transactions.TransactionInterop.GetOletxTransactionFromTransmitterPropigationToken(Byte[] propagationToken)

   --- End of inner exception stack trace ---

   at System.Transactions.Oletx.OletxTransactionManager.ProxyException(COMException comException)

   at System.Transactions.TransactionInterop.GetOletxTransactionFromTransmitterPropigationToken(Byte[] propagationToken)

   at System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx)

   at System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx)

   at System.Transactions.EnlistableStates.Promote(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.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 Csla.Data.ConnectionManager`1..ctor(String connectionString, String label)

   at Csla.Data.ConnectionManager`1.GetManager(String database, Boolean isDatabaseName, String label)

   at Csla.Data.ConnectionManager`1.GetManager(String database, Boolean isDatabaseName)

   at ******.DalManager..ctor()

   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache)

   --- End of inner exception stack trace ---

   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache)

   at System.Activator.CreateInstance(Type type, Boolean nonPublic)

   at ******.DalFactory.GetManager()

   at ******.Child_Insert()

   at lambda_method(Closure , Object , Object[] )

   at Csla.Reflection.MethodCaller.CallMethod(Object obj, DynamicMethodHandle methodHandle, Boolean hasParameters, Object[] parameters)

   --- End of inner exception stack trace ---

   at Csla.Reflection.MethodCaller.CallMethod(Object obj, DynamicMethodHandle methodHandle, Boolean hasParameters, Object[] parameters)

   at Csla.Reflection.MethodCaller.CallMethod(Object obj, String method, Boolean hasParameters, Object[] parameters)

   at Csla.Server.ChildDataPortal.Update(Object obj, Boolean hasParameters, Object[] parameters)

   --- End of inner exception stack trace ---

   at Csla.Server.ChildDataPortal.Update(Object obj, Boolean hasParameters, Object[] parameters)

   at Csla.BusinessListBase`2.Child_Update(Object[] parameters)

   at lambda_method(Closure , Object , Object[] )

   at Csla.Reflection.MethodCaller.CallMethod(Object obj, DynamicMethodHandle methodHandle, Boolean hasParameters, Object[] parameters)

   --- End of inner exception stack trace ---

   at Csla.Reflection.MethodCaller.CallMethod(Object obj, DynamicMethodHandle methodHandle, Boolean hasParameters, Object[] parameters)

   at Csla.Reflection.MethodCaller.CallMethod(Object obj, String method, Boolean hasParameters, Object[] parameters)

   at Csla.Server.ChildDataPortal.Update(Object obj, Boolean hasParameters, Object[] parameters)

   --- End of inner exception stack trace ---

   at Csla.Server.ChildDataPortal.Update(Object obj, Boolean hasParameters, Object[] parameters)

   at Csla.Core.FieldManager.FieldDataManager.UpdateChildren(Object[] parameters)

   at ******.DataPortal_Update()

   at lambda_method(Closure , Object , Object[] )

   at Csla.Reflection.MethodCaller.CallMethod(Object obj, DynamicMethodHandle methodHandle, Boolean hasParameters, Object[] parameters))

 

Thank you

Andrew

JonnyBee replied on Tuesday, September 04, 2012

Generally speaking: This means that your update is using multiple connections because you do not create and keep the connection at the "root" level. Each child update will get a new connection.

Your code should be like this:

        [Transactional(TransactionalTypes.TransactionScope)]

        protected override void DataPortal_Update()

        {
            // ALWAYS MAKE SURE THAT THE CONNECTION IS CREATED IN THE "ROOT" OBJECT
            // SO THAT IT WILL BE AVAILABLE THROUGHOUT THE ENTIRE TRANSACTION
            using (var ctx = ConnectionManager<SqlConnection>.GetManager(Database.Name))
            {

                  FieldManager.UpdateChildren();
            } 
            // HERE IS THE END OF THE CONNECTION - THE CONNECTION IS NOW CLOSED

        }

j0552 replied on Tuesday, September 04, 2012

Hi Jonny

I should have said I'm using the Encapsulated Invocation method so the DAL is in another assembly. I thought the ConnectionManager handled the connections but maybe I'm wrong or it won't work with this DAL method.

Cheers

Andrew

 

JonnyBee replied on Tuesday, September 04, 2012

The DAL may be in another assembly - so long as it uses the ConnectionManager<SqlConnection>.GetManager(Database.Name)) statement to get the connection.

BUT: The ConnectionManager uses reference counting to manage lifetime - and as soon as the "top most" ConnectionManager leaves scope the connection is closed/disposed.  This is done so you do not have to send the Connection as a parameter to all child DAL operations.

If this is not how you manage the connection in theDAL / Repository then you should not use the ConnectionManager in you BO's.

j0552 replied on Wednesday, September 05, 2012

I'm trying to follow the CSLA standard pattern encapsulated invoke pattern  to the letter. So in my example I need to know what to add to the root   DataPortal_Update method, if anything:

 

       [Transactional(TransactionalTypes.TransactionScope)]

        protected override void DataPortal_Update()

        {

            using (var dalManager = DalFactory.GetManager())

            {

                // don't need to do updates for root object so don't need the dalManager?

                // doesn't the Transactional attribute handle scope of the connections

                // i.e increment the RefCount in the ConnectionManager after the first child opens the connection?

 

                FieldManager.UpdateChildren();

            }

       }

 

Maybe the pattern is correct and there's another reason why the server tries to promote the transaction?

 

Thanks

Andrew

JonnyBee replied on Wednesday, September 05, 2012

NO, The transactional attribute tells the dataportal to use TransactionScope on the DataPortal_XYZ call.
IT DOES NOT DO ANYTHING WITH ANY CONNECTION.

So - again - your code should look like this - provided that you use ConnectionManager in your DAL code too: 

        [Transactional(TransactionalTypes.TransactionScope)]

        protected override void DataPortal_Update()

        {
            // ALWAYS MAKE SURE THAT THE CONNECTION IS CREATED IN THE "ROOT" OBJECT
            // SO THAT IT WILL BE AVAILABLE THROUGHOUT THE ENTIRE TRANSACTION
            using (var ctx = ConnectionManager<SqlConnection>.GetManager(Database.Name))
           {
               // ALL CODE IN CHILDREN THAT CALLS
               // var ctx = ConnectionManager<SqlConnection>.GetManager(Database.Name))
                 // WILL GET THE SAME CONNECTION OBJECT AS THE REFERENCE COUNT IS > 0

                  FieldManager.UpdateChildren();
            } 
            // HERE IS THE END OF THE CONNECTION - THE CONNECTION IS NOW CLOSED

        }

If in doubt - look at the ConnectionManager class source code. It is this class that does all the reference counting.
Transactional attribute / DataPortal has no knowledge of which connection manager your code will use.

You code - as shown in the first post will OPEN and CLOSE a new connnection for each child as the parent does not hold the connection manager for the entire duration of the update. Hence - you will get multiple connections and the transaction is upgraded to a distributed transaction.

j0552 replied on Wednesday, September 05, 2012

But I'm using a plugable DAL.

 

If you look at the RoleEditList.cs in the project tracker example:

   

     [Transactional(TransactionalTypes.TransactionScope)]

      protected override void DataPortal_Update()

      {

        this.RaiseListChangedEvents = false;

        using (var ctx = ProjectTracker.Dal.DalFactory.GetManager())

          Child_Update();

        this.RaiseListChangedEvents = true;

      }

 

The Child_Update in RoleEdit.cs method calls RoleDal.Update which in the EF version opens and closes  the ObjectContextManager inside the method. If I understand you correctly then you are saying that the transaction would be promoted in this example. This is not what I want and presumable not what is intended?

JonnyBee replied on Wednesday, September 05, 2012

In that pattern you should create a corresponding DalManager as in ProjectTracker that "owns" the ConnectionManager/ObjectContextManager that the DAL uses and this should be handled in the "root" DataPortal_XYZ method. The DalManager will then own the connection and define the lifetime of the object.

j0552 replied on Wednesday, September 05, 2012

Oh no! It was staring me in the face! That's how it does it. Couldn't see the wood for the trees, etc, etc.

Thanks for your help and patience.

Best wishes
Andrew

 

Copyright (c) Marimer LLC