override public void EnlistTransaction(SysTx.Transaction transaction) { SqlConnection.VerifyExecutePermission(); ValidateConnectionForExecute(null); // If a connection has a local transaction outstanding and you try // to enlist in a DTC transaction, SQL Server will rollback the // local transaction and then do the enlist (7.0 and 2000). So, if // the user tries to do this, throw. if (HasLocalTransaction) { throw ADP.LocalTransactionPresent(); } if (null != transaction && transaction.Equals(EnlistedTransaction)) { // No-op if this is the current transaction return; } // If a connection is already enlisted in a DTC transaction and you // try to enlist in another one, in 7.0 the existing DTC transaction // would roll back and then the connection would enlist in the new // one. In SQL 2000 & Yukon, when you enlist in a DTC transaction // while the connection is already enlisted in a DTC transaction, // the connection simply switches enlistments. Regardless, simply // enlist in the user specified distributed transaction. This // behavior matches OLEDB and ODBC. TdsParser bestEffortCleanupTarget = null; RuntimeHelpers.PrepareConstrainedRegions(); try { #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); #else { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection); Enlist(transaction); } #if DEBUG finally { tdsReliabilitySection.Stop(); } #endif //DEBUG } catch (System.OutOfMemoryException e) { Connection.Abort(e); throw; } catch (System.StackOverflowException e) { Connection.Abort(e); throw; } catch (System.Threading.ThreadAbortException e) { Connection.Abort(e); SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); throw; } }
// Detach transaction from connection. internal void DetachTransaction(SysTx.Transaction transaction, bool isExplicitlyReleasing) { Bid.Trace("<prov.DbConnectionInternal.DetachTransaction|RES|CPOOL> %d#, Transaction Completed. (pooledCount=%d)\n", ObjectID, _pooledCount); // potentially a multi-threaded event, so lock the connection to make sure we don't enlist in a new // transaction between compare and assignment. No need to short circuit outside of lock, since failed comparisons should // be the exception, not the rule. lock (this) { // Detach if detach-on-end behavior, or if outer connection was closed DbConnection owner = (DbConnection)Owner; if (isExplicitlyReleasing || UnbindOnTransactionCompletion || null == owner) { SysTx.Transaction currentEnlistedTransaction = _enlistedTransaction; if (currentEnlistedTransaction != null && transaction.Equals(currentEnlistedTransaction)) { EnlistedTransaction = null; if (IsTxRootWaitingForTxEnd) { DelegatedTransactionEnded(); } } } } }
protected void Enlist(SysTx.Transaction tx) { // This method should not be called while the connection has a // reference to an active delegated transaction. // Manual enlistment via SqlConnection.EnlistTransaction // should catch this case and throw an exception. // // Automatic enlistment isn't possible because // Sys.Tx keeps the connection alive until the transaction is completed. Debug.Assert (!IsNonPoolableTransactionRoot, "cannot defect an active delegated transaction!"); // potential race condition, but it's an assert if (null == tx) { if (IsEnlistedInTransaction) { EnlistNull(); } else { // When IsEnlistedInTransaction is false, it means we are in one of two states: // 1. EnlistTransaction is null, so the connection is truly not enlisted in a transaction, or // 2. Connection is enlisted in a SqlDelegatedTransaction. // // For #2, we have to consider whether or not the delegated transaction is active. // If it is not active, we allow the enlistment in the NULL transaction. // // If it is active, technically this is an error. // However, no exception is thrown as this was the precedent (and this case is silently ignored, no error, but no enlistment either). // There are two mitigations for this: // 1. SqlConnection.EnlistTransaction checks that the enlisted transaction has completed before allowing a different enlistment. // 2. For debug builds, the assert at the beginning of this method checks for an enlistment in an active delegated transaction. SysTx.Transaction enlistedTransaction = EnlistedTransaction; if (enlistedTransaction != null && enlistedTransaction.TransactionInformation.Status != SysTx.TransactionStatus.Active) { EnlistNull(); } } } // Only enlist if it's different... else if (!tx.Equals(EnlistedTransaction)) { // WebData 20000024 - Must use Equals, not != EnlistNonNull(tx); } }