SqlInternalConnection.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / whidbey / NetFXspW7 / ndp / fx / src / Data / System / Data / SqlClient / SqlInternalConnection.cs / 1 / SqlInternalConnection.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// [....] 
// [....]
//----------------------------------------------------------------------------- 
 
namespace System.Data.SqlClient
{ 
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Common; 
    using System.Data.ProviderBase;
    using System.Diagnostics; 
    using System.Globalization; 
    using System.Reflection;
    using System.Runtime.CompilerServices; 
    using System.Runtime.ConstrainedExecution;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions; 
    using System.Text;
    using System.Threading; 
    using SysTx = System.Transactions; 

    abstract internal class SqlInternalConnection : DbConnectionInternal { 
        private readonly SqlConnectionString _connectionOptions;
        private string                       _currentDatabase;
        private string                       _currentDataSource;
        private bool                         _isEnlistedInTransaction; // is the server-side connection enlisted? true while we're enlisted, reset only after we send a null... 
        private byte[]                       _promotedDTCToken;        // token returned by the server when we promote transaction
        private SqlDelegatedTransaction      _delegatedTransaction;    // the delegated (or promoted) transaction we're responsible for. 
        private byte[]                       _whereAbouts;             // cache the whereabouts (DTC Address) for exporting 

        internal enum TransactionRequest { 
            Begin,
            Promote,
            Commit,
            Rollback, 
            IfRollback,
            Save 
        }; 

        internal SqlInternalConnection(SqlConnectionString connectionOptions) : base() { 
            Debug.Assert(null != connectionOptions, "null connectionOptions?");
            _connectionOptions = connectionOptions;
        }
 
        internal SqlConnection Connection {
            get { 
                return (SqlConnection)Owner; 
            }
        } 

        internal SqlConnectionString ConnectionOptions {
            get {
                return _connectionOptions; 
            }
        } 
 
        internal string CurrentDatabase {
            get { 
                return _currentDatabase;
            }
            set {
                _currentDatabase = value; 
            }
        } 
 
        internal string CurrentDataSource {
            get { 
                return _currentDataSource;
            }
            set {
                _currentDataSource = value; 
            }
        } 
 
        internal SqlDelegatedTransaction DelegatedTransaction {
            get { 
                return _delegatedTransaction;
            }
            set {
                _delegatedTransaction = value; 
            }
        } 
 
        abstract internal SqlInternalTransaction CurrentTransaction {
            get; 
        }

        // SQLBU 415870
        //  Get the internal transaction that should be hooked to a new outer transaction 
        //  during a BeginTransaction API call.  In some cases (i.e. connection is going to
        //  be reset), CurrentTransaction should not be hooked up this way. 
        virtual internal SqlInternalTransaction AvailableInternalTransaction { 
            get {
                return CurrentTransaction; 
            }
        }

        abstract internal SqlInternalTransaction PendingTransaction { 
            get;
        } 
 
        override internal bool RequireExplicitTransactionUnbind {
            get { 
                return _connectionOptions.TransactionBinding == SqlConnectionString.TransactionBindingEnum.ExplicitUnbind;
            }
        }
 
        override protected internal bool IsNonPoolableTransactionRoot {
            get { 
                return IsTransactionRoot;  // default behavior is that root transactions are NOT poolable.  Subclasses may override. 
            }
        } 

        override internal bool IsTransactionRoot {
            get {
                if (null == _delegatedTransaction) { 
                    return false;
                } 
 
                lock (this) {
                    return (null != _delegatedTransaction && _delegatedTransaction.IsActive); 
                }
            }
        }
 
        internal bool HasLocalTransaction {
            get { 
                SqlInternalTransaction currentTransaction = CurrentTransaction; 
                bool result = (null != currentTransaction && currentTransaction.IsLocal);
                return result; 
            }
        }

        internal bool HasLocalTransactionFromAPI { 
            get {
                SqlInternalTransaction currentTransaction = CurrentTransaction; 
                bool result = (null != currentTransaction && currentTransaction.HasParentTransaction); 
                return result;
            } 
        }

        internal bool IsEnlistedInTransaction {
            get { 
                return _isEnlistedInTransaction;
            } 
        } 

        // Workaround to access context transaction without rewriting connection pool & internalconnections properly. 
        // Context transactions SHOULD be considered enlisted.
        //   This works for now only because we can't unenlist from the context transaction
        // DON'T START USING THIS ANYWHERE EXCEPT IN InternalTransaction and in InternalConnectionSmi!!!
        private SysTx.Transaction _contextTransaction; 
        protected internal SysTx.Transaction ContextTransaction {
            get { 
                return _contextTransaction; 
            }
            set { 
                _contextTransaction = value;
            }
        }
 
        internal SysTx.Transaction InternalEnlistedTransaction {
            get { 
                // Workaround to access context transaction without rewriting connection pool & internalconnections properly. 
                // This SHOULD be a simple wrapper around EnlistedTransaction.
                //   This works for now only because we can't unenlist from the context transaction 
                SysTx.Transaction tx = EnlistedTransaction;

                if (null == tx) {
                    tx = ContextTransaction; 
                }
 
                return tx; 
            }
        } 

        abstract internal bool IsLockedForBulkCopy {
            get;
        } 

        abstract internal bool IsShiloh { 
            get; 
        }
 
        abstract internal bool IsYukonOrNewer {
            get;
        }
 
        abstract internal bool IsKatmaiOrNewer {
            get; 
        } 

#if WINFSFunctionality 
        abstract internal bool IsWinFS {
            get;
        }
#endif 

        internal byte[] PromotedDTCToken { 
            get { 
                return _promotedDTCToken;
            } 
            set {
                _promotedDTCToken = value;
            }
        } 

        abstract internal void AddPreparedCommand(SqlCommand cmd); 
 
        override public DbTransaction BeginTransaction(IsolationLevel iso) {
            return BeginSqlTransaction(iso, null); 
        }

        virtual internal SqlTransaction BeginSqlTransaction(IsolationLevel iso, string transactionName) {
            SqlStatistics statistics = null; 
            SNIHandle bestEffortCleanupTarget = null;
            RuntimeHelpers.PrepareConstrainedRegions(); 
            try { 
#if DEBUG
                object initialReliabilitySlotValue = Thread.GetData(TdsParser.ReliabilitySlot); 

                RuntimeHelpers.PrepareConstrainedRegions();
                try {
                    Thread.SetData(TdsParser.ReliabilitySlot, true); 
#endif //DEBUG
                    bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection); 
                    statistics = SqlStatistics.StartTimer(Connection.Statistics); 

                    SqlConnection.ExecutePermission.Demand(); // MDAC 81476 

                    ValidateConnectionForExecute(null);

                    if (HasLocalTransactionFromAPI) 
                        throw ADP.ParallelTransactionsNotSupported(Connection);
 
                    if (iso == IsolationLevel.Unspecified) { 
                        iso = IsolationLevel.ReadCommitted; // Default to ReadCommitted if unspecified.
                    } 

                    SqlTransaction transaction = new SqlTransaction(this, Connection, iso, AvailableInternalTransaction);
                    ExecuteTransaction(TransactionRequest.Begin, transactionName, iso, transaction.InternalTransaction, false);
                    return transaction; 
#if DEBUG
                } 
                finally { 
                    Thread.SetData(TdsParser.ReliabilitySlot, initialReliabilitySlotValue);
                } 
#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;
            } 
            finally { 
                SqlStatistics.StopTimer(statistics);
            } 
        }

        override public void ChangeDatabase(string database) {
            SqlConnection.ExecutePermission.Demand(); // MDAC 80961 

            if (ADP.IsEmpty(database)) { 
                throw ADP.EmptyDatabaseName(); 
            }
 
            ValidateConnectionForExecute(null); //

            ChangeDatabaseInternal(database);  // do the real work...
        } 

        abstract protected void ChangeDatabaseInternal(string database); 
 
        abstract internal void ClearPreparedCommands();
 
        // SQLBU 411265
        //  prepared commands need to be cleared out before the outer connection disconnects from the inner connection
        //  Overriding CloseConnection allows us to do this.  Long term, it would be better to manage this entirely on
        //  the internal connection, but that's a much larger change. 
        internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) {
            if (!IsConnectionDoomed) { 
                ClearPreparedCommands(); 
            }
 
            base.CloseConnection(owningObject, connectionFactory);
        }

        override protected void CleanupTransactionOnCompletion(SysTx.Transaction transaction) { 
            // Note: unlocked, potentially multi-threaded code, so pull delegate to local to
            //  ensure it doesn't change between test and call. 
            SqlDelegatedTransaction delegatedTransaction = DelegatedTransaction; 
            if (null != delegatedTransaction) {
                delegatedTransaction.TransactionEnded(transaction); 
            }
        }

        override protected DbReferenceCollection CreateReferenceCollection() { 
            return new SqlReferenceCollection();
        } 
 
        override protected void Deactivate() {
            if (Bid.AdvancedOn) { 
                Bid.Trace(" %d# deactivating\n", base.ObjectID);
            }
            SNIHandle bestEffortCleanupTarget = null;
            RuntimeHelpers.PrepareConstrainedRegions(); 
            try {
 
#if DEBUG 
                object initialReliabilitySlotValue = Thread.GetData(TdsParser.ReliabilitySlot);
 
                RuntimeHelpers.PrepareConstrainedRegions();
                try {
                    Thread.SetData(TdsParser.ReliabilitySlot, true);
#endif //DEBUG 
                    bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection);
                    SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection; 
                    if (null != referenceCollection) { 
                        referenceCollection.Deactivate();
                    } 

                    // Invoke subclass-specific deactivation logic
                    InternalDeactivate();
#if DEBUG 
                }
                finally { 
                    Thread.SetData(TdsParser.ReliabilitySlot, initialReliabilitySlotValue); 
                }
#endif //DEBUG 
            }
            catch (System.OutOfMemoryException) {
                DoomThisConnection();
                throw; 
            }
            catch (System.StackOverflowException) { 
                DoomThisConnection(); 
                throw;
            } 
            catch (System.Threading.ThreadAbortException) {
                DoomThisConnection();
                SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
                throw; 
            }
            catch (Exception e) { 
                // 
                if (!ADP.IsCatchableExceptionType(e)) {
                    throw; 
                }

                // if an exception occurred, the inner connection will be
                // marked as unusable and destroyed upon returning to the 
                // pool
                DoomThisConnection(); 
 
                ADP.TraceExceptionWithoutRethrow(e);
            } 
        }

        abstract internal void DisconnectTransaction(SqlInternalTransaction internalTransaction);
 
        override public void Dispose() {
            _whereAbouts = null; 
            base.Dispose(); 
        }
 
        protected void Enlist(SysTx.Transaction tx) {
            // We should never be calling this method once we've created a
            // delegated transaction, because the Manual enlistment should
            // have caught this and thrown an exception, and the Auto enlistment 
            // isn't possible because Sys.Tx keeps the connection alive until
            // the transaction is completed. 
            Debug.Assert (!IsNonPoolableTransactionRoot, "cannot defect a delegated transaction!");  // potential race condition, but it's an assert 

            if (null == tx) { 
                 if (IsEnlistedInTransaction) {
                    EnlistNull();
                }
            } 
            // Only enlist if it's different...
            else if (!tx.Equals(EnlistedTransaction)) { // WebData 20000024 - Must use Equals, not != 
                EnlistNonNull(tx); 
            }
        } 

        private void EnlistNonNull(SysTx.Transaction tx) {
            Debug.Assert(null != tx, "null transaction?");
 
            if (Bid.AdvancedOn) {
                Bid.Trace(" %d#, transaction %d#.\n", base.ObjectID, tx.GetHashCode()); 
            } 

            bool hasDelegatedTransaction = false; 

            if (IsYukonOrNewer) {
                if (Bid.AdvancedOn) {
                    Bid.Trace(" %d#, attempting to delegate\n", base.ObjectID); 
                }
 
                // Promotable transactions are only supported on Yukon 
                // servers or newer.
                SqlDelegatedTransaction delegatedTransaction = new SqlDelegatedTransaction(this, tx); 

                try {
                    // NOTE: System.Transactions claims to resolve all
                    // potential race conditions between multiple delegate 
                    // requests of the same transaction to different
                    // connections in their code, such that only one 
                    // attempt to delegate will succeed. 

                    // NOTE: PromotableSinglePhaseEnlist will eventually 
                    // make a round trip to the server; doing this inside
                    // a lock is not the best choice.  We presume that you
                    // aren't trying to enlist concurrently on two threads
                    // and leave it at that -- We don't claim any thread 
                    // safety with regard to multiple concurrent requests
                    // to enlist the same connection in different 
                    // transactions, which is good, because we don't have 
                    // it anyway.
 
                    // PromotableSinglePhaseEnlist may not actually promote
                    // the transaction when it is already delegated (this is
                    // the way they resolve the race condition when two
                    // threads attempt to delegate the same Lightweight 
                    // Transaction)  In that case, we can safely ignore
                    // our delegated transaction, and proceed to enlist 
                    // in the promoted one. 

                    if (tx.EnlistPromotableSinglePhase(delegatedTransaction)) { 
                        hasDelegatedTransaction = true;

                        _delegatedTransaction  = delegatedTransaction;
 
                        if (Bid.AdvancedOn) {
                            long transactionId = SqlInternalTransaction.NullTransactionId; 
                            int transactionObjectID = 0; 
                            if (null != CurrentTransaction) {
                                transactionId = CurrentTransaction.TransactionId; 
                                transactionObjectID = CurrentTransaction.ObjectID;
                            }
                            Bid.Trace(" %d#, delegated to transaction %d# with transactionId=0x%I64x\n", base.ObjectID, transactionObjectID, transactionId);
                        } 
                    }
                } 
                catch (SqlException e) { 
                    // we do not want to eat the error if it is a fatal one
                    if (e.Class >= TdsEnums.FATAL_ERROR_CLASS) { 
                        throw;
                    }

                    // if the parser is not openloggedin, the connection is no longer good 
                    SqlInternalConnectionTds tdsConnection = this as SqlInternalConnectionTds;
                    if (tdsConnection != null) { 
                        if (tdsConnection.Parser.State != TdsParserState.OpenLoggedIn) { 
                            throw;
                        } 
                    }

                    ADP.TraceExceptionWithoutRethrow(e);
 
                    // In this case, SqlDelegatedTransaction.Initialize
                    // failed and we don't necessarily want to reject 
                    // things -- there may have been a legitimate reason 
                    // for the failure.
                } 
            }

            if (!hasDelegatedTransaction) {
                if (Bid.AdvancedOn) { 
                    Bid.Trace(" %d#, delegation not possible, enlisting.\n", base.ObjectID);
                } 
 
                byte[] cookie = null;
 
                if (null == _whereAbouts) {
                     byte[] dtcAddress = GetDTCAddress();

                     if (null == dtcAddress) { 
                        throw SQL.CannotGetDTCAddress();
                     } 
                     _whereAbouts = dtcAddress; 
                }
 
                cookie = GetTransactionCookie(tx, _whereAbouts);

                // send cookie to server to finish enlistment
                PropagateTransactionCookie(cookie); 

                _isEnlistedInTransaction = true; 
 
                if (Bid.AdvancedOn) {
                    long transactionId = SqlInternalTransaction.NullTransactionId; 
                    int transactionObjectID = 0;
                    if (null != CurrentTransaction) {
                        transactionId = CurrentTransaction.TransactionId;
                        transactionObjectID = CurrentTransaction.ObjectID; 
                    }
                    Bid.Trace(" %d#, enlisted with transaction %d# with transactionId=0x%I64x\n", base.ObjectID, transactionObjectID, transactionId); 
                } 
            }
 
            EnlistedTransaction = tx; // Tell the base class about our enlistment


            // If we're on a Yukon or newer server, and we we delegate the 
            // transaction successfully, we will have done a begin transaction,
            // which produces a transaction id that we should execute all requests 
            // on.  The TdsParser or SmiEventSink will store this information as 
            // the current transaction.
            // 
            // Likewise, propagating a transaction to a Yukon or newer server will
            // produce a transaction id that The TdsParser or SmiEventSink will
            // store as the current transaction.
            // 
            // In either case, when we're working with a Yukon or newer server
            // we better have a current transaction by now. 
 
            Debug.Assert(!IsYukonOrNewer || null != CurrentTransaction, "delegated/enlisted transaction with null current transaction?");
        } 

        internal void EnlistNull() {
            if (Bid.AdvancedOn) {
                Bid.Trace(" %d#, unenlisting.\n", base.ObjectID); 
            }
 
            // We were in a transaction, but now we are not - so send 
            // message to server with empty transaction - confirmed proper
            // behavior from Sameet Agarwal 
            //
            // The connection pooler maintains separate pools for enlisted
            // transactions, and only when that transaction is committed or
            // rolled back will those connections be taken from that 
            // separate pool and returned to the general pool of connections
            // that are not affiliated with any transactions.  When this 
            // occurs, we will have a new transaction of null and we are 
            // required to send an empty transaction payload to the server.
 
            PropagateTransactionCookie(null);

            _isEnlistedInTransaction = false;
            EnlistedTransaction = null; // Tell the base class about our enlistment 

            if (Bid.AdvancedOn) { 
                Bid.Trace(" %d#, unenlisted.\n", base.ObjectID); 
            }
 
            // The EnlistTransaction above will return an TransactionEnded event,
            // which causes the TdsParser or SmiEventSink should to clear the
            // current transaction.
            // 
            // In either case, when we're working with a Yukon or newer server
            // we better not have a current transaction at this point. 
 
            Debug.Assert(!IsYukonOrNewer || null == CurrentTransaction, "unenlisted transaction with non-null current transaction?");   // verify it!
        } 

        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. 

            SNIHandle bestEffortCleanupTarget = null;
            RuntimeHelpers.PrepareConstrainedRegions();
            try { 
#if DEBUG
                object initialReliabilitySlotValue = Thread.GetData(TdsParser.ReliabilitySlot); 
 
                RuntimeHelpers.PrepareConstrainedRegions();
                try { 
                    Thread.SetData(TdsParser.ReliabilitySlot, true);
#endif //DEBUG
                    bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection);
                    Enlist(transaction); 
#if DEBUG
                } 
                finally { 
                    Thread.SetData(TdsParser.ReliabilitySlot, initialReliabilitySlotValue);
                } 
#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;
            } 
        } 

        abstract internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest); 

        internal SqlDataReader FindLiveReader(SqlCommand command) {
            SqlDataReader reader = null;
            SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection; 
            if (null != referenceCollection) {
                reader =  referenceCollection.FindLiveReader(command); 
            } 
            return reader;
        } 

        static internal SNIHandle GetBestEffortCleanupTarget(SqlConnection connection) {
            if (null != connection) {
                SqlInternalConnectionTds innerConnection = (connection.InnerConnection as SqlInternalConnectionTds); 
                if (null != innerConnection) {
                    TdsParser parser = innerConnection.Parser; 
                    if (parser != null) { 
                        return parser.GetBestEffortCleanupTarget();
                    } 
                }
            }

            return null; 
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 
        static internal void BestEffortCleanup(SNIHandle target) {
            if (null != target) { 
                target.Dispose();
            }
        }
 
        abstract protected byte[] GetDTCAddress();
 
        static private byte[] GetTransactionCookie(SysTx.Transaction transaction, byte[] whereAbouts) { 
            byte[] transactionCookie = null;
            if (null != transaction) { 
                transactionCookie = SysTx.TransactionInterop.GetExportCookie(transaction, whereAbouts);
            }
            return transactionCookie;
        } 

        virtual protected void InternalDeactivate() { 
        } 

        internal void OnError(SqlException exception, bool breakConnection) { 
            if (breakConnection)
                DoomThisConnection();

            if (null != Connection) { 
                Connection.OnError(exception, breakConnection);
            } 
            else if (exception.Class >= TdsEnums.MIN_ERROR_CLASS) { 
                // It is an error, and should be thrown.  Class of TdsEnums.MIN_ERROR_CLASS
                // or above is an error, below TdsEnums.MIN_ERROR_CLASS denotes an info message. 
                throw exception;
            }
        }
 
        abstract protected void PropagateTransactionCookie(byte[] transactionCookie);
 
        abstract internal void RemovePreparedCommand(SqlCommand cmd); 

        abstract internal void ValidateConnectionForExecute(SqlCommand command); 

        // This validation step MUST be done after locking the connection to guarantee we don't
        //  accidentally execute after the transaction has completed on a different thread, causing us to unwittingly
        //  execute in auto-commit mode. 
        internal void ValidateTransaction() {
            if (RequireExplicitTransactionUnbind) { 
 
                // If we have a system transaction, check that transaction is active and is the current transaction
                if (null != InternalEnlistedTransaction) { 
                    SysTx.Transaction currentTran = SysTx.Transaction.Current;
                    if (SysTx.TransactionStatus.Active != InternalEnlistedTransaction.TransactionInformation.Status ||
                            null == currentTran ||
                            !InternalEnlistedTransaction.Equals(currentTran)) { 
                        throw ADP.TransactionConnectionMismatch();
                    } 
                } 
            }
        } 


    }
} 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// [....] 
// [....]
//----------------------------------------------------------------------------- 
 
namespace System.Data.SqlClient
{ 
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Common; 
    using System.Data.ProviderBase;
    using System.Diagnostics; 
    using System.Globalization; 
    using System.Reflection;
    using System.Runtime.CompilerServices; 
    using System.Runtime.ConstrainedExecution;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions; 
    using System.Text;
    using System.Threading; 
    using SysTx = System.Transactions; 

    abstract internal class SqlInternalConnection : DbConnectionInternal { 
        private readonly SqlConnectionString _connectionOptions;
        private string                       _currentDatabase;
        private string                       _currentDataSource;
        private bool                         _isEnlistedInTransaction; // is the server-side connection enlisted? true while we're enlisted, reset only after we send a null... 
        private byte[]                       _promotedDTCToken;        // token returned by the server when we promote transaction
        private SqlDelegatedTransaction      _delegatedTransaction;    // the delegated (or promoted) transaction we're responsible for. 
        private byte[]                       _whereAbouts;             // cache the whereabouts (DTC Address) for exporting 

        internal enum TransactionRequest { 
            Begin,
            Promote,
            Commit,
            Rollback, 
            IfRollback,
            Save 
        }; 

        internal SqlInternalConnection(SqlConnectionString connectionOptions) : base() { 
            Debug.Assert(null != connectionOptions, "null connectionOptions?");
            _connectionOptions = connectionOptions;
        }
 
        internal SqlConnection Connection {
            get { 
                return (SqlConnection)Owner; 
            }
        } 

        internal SqlConnectionString ConnectionOptions {
            get {
                return _connectionOptions; 
            }
        } 
 
        internal string CurrentDatabase {
            get { 
                return _currentDatabase;
            }
            set {
                _currentDatabase = value; 
            }
        } 
 
        internal string CurrentDataSource {
            get { 
                return _currentDataSource;
            }
            set {
                _currentDataSource = value; 
            }
        } 
 
        internal SqlDelegatedTransaction DelegatedTransaction {
            get { 
                return _delegatedTransaction;
            }
            set {
                _delegatedTransaction = value; 
            }
        } 
 
        abstract internal SqlInternalTransaction CurrentTransaction {
            get; 
        }

        // SQLBU 415870
        //  Get the internal transaction that should be hooked to a new outer transaction 
        //  during a BeginTransaction API call.  In some cases (i.e. connection is going to
        //  be reset), CurrentTransaction should not be hooked up this way. 
        virtual internal SqlInternalTransaction AvailableInternalTransaction { 
            get {
                return CurrentTransaction; 
            }
        }

        abstract internal SqlInternalTransaction PendingTransaction { 
            get;
        } 
 
        override internal bool RequireExplicitTransactionUnbind {
            get { 
                return _connectionOptions.TransactionBinding == SqlConnectionString.TransactionBindingEnum.ExplicitUnbind;
            }
        }
 
        override protected internal bool IsNonPoolableTransactionRoot {
            get { 
                return IsTransactionRoot;  // default behavior is that root transactions are NOT poolable.  Subclasses may override. 
            }
        } 

        override internal bool IsTransactionRoot {
            get {
                if (null == _delegatedTransaction) { 
                    return false;
                } 
 
                lock (this) {
                    return (null != _delegatedTransaction && _delegatedTransaction.IsActive); 
                }
            }
        }
 
        internal bool HasLocalTransaction {
            get { 
                SqlInternalTransaction currentTransaction = CurrentTransaction; 
                bool result = (null != currentTransaction && currentTransaction.IsLocal);
                return result; 
            }
        }

        internal bool HasLocalTransactionFromAPI { 
            get {
                SqlInternalTransaction currentTransaction = CurrentTransaction; 
                bool result = (null != currentTransaction && currentTransaction.HasParentTransaction); 
                return result;
            } 
        }

        internal bool IsEnlistedInTransaction {
            get { 
                return _isEnlistedInTransaction;
            } 
        } 

        // Workaround to access context transaction without rewriting connection pool & internalconnections properly. 
        // Context transactions SHOULD be considered enlisted.
        //   This works for now only because we can't unenlist from the context transaction
        // DON'T START USING THIS ANYWHERE EXCEPT IN InternalTransaction and in InternalConnectionSmi!!!
        private SysTx.Transaction _contextTransaction; 
        protected internal SysTx.Transaction ContextTransaction {
            get { 
                return _contextTransaction; 
            }
            set { 
                _contextTransaction = value;
            }
        }
 
        internal SysTx.Transaction InternalEnlistedTransaction {
            get { 
                // Workaround to access context transaction without rewriting connection pool & internalconnections properly. 
                // This SHOULD be a simple wrapper around EnlistedTransaction.
                //   This works for now only because we can't unenlist from the context transaction 
                SysTx.Transaction tx = EnlistedTransaction;

                if (null == tx) {
                    tx = ContextTransaction; 
                }
 
                return tx; 
            }
        } 

        abstract internal bool IsLockedForBulkCopy {
            get;
        } 

        abstract internal bool IsShiloh { 
            get; 
        }
 
        abstract internal bool IsYukonOrNewer {
            get;
        }
 
        abstract internal bool IsKatmaiOrNewer {
            get; 
        } 

#if WINFSFunctionality 
        abstract internal bool IsWinFS {
            get;
        }
#endif 

        internal byte[] PromotedDTCToken { 
            get { 
                return _promotedDTCToken;
            } 
            set {
                _promotedDTCToken = value;
            }
        } 

        abstract internal void AddPreparedCommand(SqlCommand cmd); 
 
        override public DbTransaction BeginTransaction(IsolationLevel iso) {
            return BeginSqlTransaction(iso, null); 
        }

        virtual internal SqlTransaction BeginSqlTransaction(IsolationLevel iso, string transactionName) {
            SqlStatistics statistics = null; 
            SNIHandle bestEffortCleanupTarget = null;
            RuntimeHelpers.PrepareConstrainedRegions(); 
            try { 
#if DEBUG
                object initialReliabilitySlotValue = Thread.GetData(TdsParser.ReliabilitySlot); 

                RuntimeHelpers.PrepareConstrainedRegions();
                try {
                    Thread.SetData(TdsParser.ReliabilitySlot, true); 
#endif //DEBUG
                    bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection); 
                    statistics = SqlStatistics.StartTimer(Connection.Statistics); 

                    SqlConnection.ExecutePermission.Demand(); // MDAC 81476 

                    ValidateConnectionForExecute(null);

                    if (HasLocalTransactionFromAPI) 
                        throw ADP.ParallelTransactionsNotSupported(Connection);
 
                    if (iso == IsolationLevel.Unspecified) { 
                        iso = IsolationLevel.ReadCommitted; // Default to ReadCommitted if unspecified.
                    } 

                    SqlTransaction transaction = new SqlTransaction(this, Connection, iso, AvailableInternalTransaction);
                    ExecuteTransaction(TransactionRequest.Begin, transactionName, iso, transaction.InternalTransaction, false);
                    return transaction; 
#if DEBUG
                } 
                finally { 
                    Thread.SetData(TdsParser.ReliabilitySlot, initialReliabilitySlotValue);
                } 
#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;
            } 
            finally { 
                SqlStatistics.StopTimer(statistics);
            } 
        }

        override public void ChangeDatabase(string database) {
            SqlConnection.ExecutePermission.Demand(); // MDAC 80961 

            if (ADP.IsEmpty(database)) { 
                throw ADP.EmptyDatabaseName(); 
            }
 
            ValidateConnectionForExecute(null); //

            ChangeDatabaseInternal(database);  // do the real work...
        } 

        abstract protected void ChangeDatabaseInternal(string database); 
 
        abstract internal void ClearPreparedCommands();
 
        // SQLBU 411265
        //  prepared commands need to be cleared out before the outer connection disconnects from the inner connection
        //  Overriding CloseConnection allows us to do this.  Long term, it would be better to manage this entirely on
        //  the internal connection, but that's a much larger change. 
        internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) {
            if (!IsConnectionDoomed) { 
                ClearPreparedCommands(); 
            }
 
            base.CloseConnection(owningObject, connectionFactory);
        }

        override protected void CleanupTransactionOnCompletion(SysTx.Transaction transaction) { 
            // Note: unlocked, potentially multi-threaded code, so pull delegate to local to
            //  ensure it doesn't change between test and call. 
            SqlDelegatedTransaction delegatedTransaction = DelegatedTransaction; 
            if (null != delegatedTransaction) {
                delegatedTransaction.TransactionEnded(transaction); 
            }
        }

        override protected DbReferenceCollection CreateReferenceCollection() { 
            return new SqlReferenceCollection();
        } 
 
        override protected void Deactivate() {
            if (Bid.AdvancedOn) { 
                Bid.Trace(" %d# deactivating\n", base.ObjectID);
            }
            SNIHandle bestEffortCleanupTarget = null;
            RuntimeHelpers.PrepareConstrainedRegions(); 
            try {
 
#if DEBUG 
                object initialReliabilitySlotValue = Thread.GetData(TdsParser.ReliabilitySlot);
 
                RuntimeHelpers.PrepareConstrainedRegions();
                try {
                    Thread.SetData(TdsParser.ReliabilitySlot, true);
#endif //DEBUG 
                    bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection);
                    SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection; 
                    if (null != referenceCollection) { 
                        referenceCollection.Deactivate();
                    } 

                    // Invoke subclass-specific deactivation logic
                    InternalDeactivate();
#if DEBUG 
                }
                finally { 
                    Thread.SetData(TdsParser.ReliabilitySlot, initialReliabilitySlotValue); 
                }
#endif //DEBUG 
            }
            catch (System.OutOfMemoryException) {
                DoomThisConnection();
                throw; 
            }
            catch (System.StackOverflowException) { 
                DoomThisConnection(); 
                throw;
            } 
            catch (System.Threading.ThreadAbortException) {
                DoomThisConnection();
                SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
                throw; 
            }
            catch (Exception e) { 
                // 
                if (!ADP.IsCatchableExceptionType(e)) {
                    throw; 
                }

                // if an exception occurred, the inner connection will be
                // marked as unusable and destroyed upon returning to the 
                // pool
                DoomThisConnection(); 
 
                ADP.TraceExceptionWithoutRethrow(e);
            } 
        }

        abstract internal void DisconnectTransaction(SqlInternalTransaction internalTransaction);
 
        override public void Dispose() {
            _whereAbouts = null; 
            base.Dispose(); 
        }
 
        protected void Enlist(SysTx.Transaction tx) {
            // We should never be calling this method once we've created a
            // delegated transaction, because the Manual enlistment should
            // have caught this and thrown an exception, and the Auto enlistment 
            // isn't possible because Sys.Tx keeps the connection alive until
            // the transaction is completed. 
            Debug.Assert (!IsNonPoolableTransactionRoot, "cannot defect a delegated transaction!");  // potential race condition, but it's an assert 

            if (null == tx) { 
                 if (IsEnlistedInTransaction) {
                    EnlistNull();
                }
            } 
            // Only enlist if it's different...
            else if (!tx.Equals(EnlistedTransaction)) { // WebData 20000024 - Must use Equals, not != 
                EnlistNonNull(tx); 
            }
        } 

        private void EnlistNonNull(SysTx.Transaction tx) {
            Debug.Assert(null != tx, "null transaction?");
 
            if (Bid.AdvancedOn) {
                Bid.Trace(" %d#, transaction %d#.\n", base.ObjectID, tx.GetHashCode()); 
            } 

            bool hasDelegatedTransaction = false; 

            if (IsYukonOrNewer) {
                if (Bid.AdvancedOn) {
                    Bid.Trace(" %d#, attempting to delegate\n", base.ObjectID); 
                }
 
                // Promotable transactions are only supported on Yukon 
                // servers or newer.
                SqlDelegatedTransaction delegatedTransaction = new SqlDelegatedTransaction(this, tx); 

                try {
                    // NOTE: System.Transactions claims to resolve all
                    // potential race conditions between multiple delegate 
                    // requests of the same transaction to different
                    // connections in their code, such that only one 
                    // attempt to delegate will succeed. 

                    // NOTE: PromotableSinglePhaseEnlist will eventually 
                    // make a round trip to the server; doing this inside
                    // a lock is not the best choice.  We presume that you
                    // aren't trying to enlist concurrently on two threads
                    // and leave it at that -- We don't claim any thread 
                    // safety with regard to multiple concurrent requests
                    // to enlist the same connection in different 
                    // transactions, which is good, because we don't have 
                    // it anyway.
 
                    // PromotableSinglePhaseEnlist may not actually promote
                    // the transaction when it is already delegated (this is
                    // the way they resolve the race condition when two
                    // threads attempt to delegate the same Lightweight 
                    // Transaction)  In that case, we can safely ignore
                    // our delegated transaction, and proceed to enlist 
                    // in the promoted one. 

                    if (tx.EnlistPromotableSinglePhase(delegatedTransaction)) { 
                        hasDelegatedTransaction = true;

                        _delegatedTransaction  = delegatedTransaction;
 
                        if (Bid.AdvancedOn) {
                            long transactionId = SqlInternalTransaction.NullTransactionId; 
                            int transactionObjectID = 0; 
                            if (null != CurrentTransaction) {
                                transactionId = CurrentTransaction.TransactionId; 
                                transactionObjectID = CurrentTransaction.ObjectID;
                            }
                            Bid.Trace(" %d#, delegated to transaction %d# with transactionId=0x%I64x\n", base.ObjectID, transactionObjectID, transactionId);
                        } 
                    }
                } 
                catch (SqlException e) { 
                    // we do not want to eat the error if it is a fatal one
                    if (e.Class >= TdsEnums.FATAL_ERROR_CLASS) { 
                        throw;
                    }

                    // if the parser is not openloggedin, the connection is no longer good 
                    SqlInternalConnectionTds tdsConnection = this as SqlInternalConnectionTds;
                    if (tdsConnection != null) { 
                        if (tdsConnection.Parser.State != TdsParserState.OpenLoggedIn) { 
                            throw;
                        } 
                    }

                    ADP.TraceExceptionWithoutRethrow(e);
 
                    // In this case, SqlDelegatedTransaction.Initialize
                    // failed and we don't necessarily want to reject 
                    // things -- there may have been a legitimate reason 
                    // for the failure.
                } 
            }

            if (!hasDelegatedTransaction) {
                if (Bid.AdvancedOn) { 
                    Bid.Trace(" %d#, delegation not possible, enlisting.\n", base.ObjectID);
                } 
 
                byte[] cookie = null;
 
                if (null == _whereAbouts) {
                     byte[] dtcAddress = GetDTCAddress();

                     if (null == dtcAddress) { 
                        throw SQL.CannotGetDTCAddress();
                     } 
                     _whereAbouts = dtcAddress; 
                }
 
                cookie = GetTransactionCookie(tx, _whereAbouts);

                // send cookie to server to finish enlistment
                PropagateTransactionCookie(cookie); 

                _isEnlistedInTransaction = true; 
 
                if (Bid.AdvancedOn) {
                    long transactionId = SqlInternalTransaction.NullTransactionId; 
                    int transactionObjectID = 0;
                    if (null != CurrentTransaction) {
                        transactionId = CurrentTransaction.TransactionId;
                        transactionObjectID = CurrentTransaction.ObjectID; 
                    }
                    Bid.Trace(" %d#, enlisted with transaction %d# with transactionId=0x%I64x\n", base.ObjectID, transactionObjectID, transactionId); 
                } 
            }
 
            EnlistedTransaction = tx; // Tell the base class about our enlistment


            // If we're on a Yukon or newer server, and we we delegate the 
            // transaction successfully, we will have done a begin transaction,
            // which produces a transaction id that we should execute all requests 
            // on.  The TdsParser or SmiEventSink will store this information as 
            // the current transaction.
            // 
            // Likewise, propagating a transaction to a Yukon or newer server will
            // produce a transaction id that The TdsParser or SmiEventSink will
            // store as the current transaction.
            // 
            // In either case, when we're working with a Yukon or newer server
            // we better have a current transaction by now. 
 
            Debug.Assert(!IsYukonOrNewer || null != CurrentTransaction, "delegated/enlisted transaction with null current transaction?");
        } 

        internal void EnlistNull() {
            if (Bid.AdvancedOn) {
                Bid.Trace(" %d#, unenlisting.\n", base.ObjectID); 
            }
 
            // We were in a transaction, but now we are not - so send 
            // message to server with empty transaction - confirmed proper
            // behavior from Sameet Agarwal 
            //
            // The connection pooler maintains separate pools for enlisted
            // transactions, and only when that transaction is committed or
            // rolled back will those connections be taken from that 
            // separate pool and returned to the general pool of connections
            // that are not affiliated with any transactions.  When this 
            // occurs, we will have a new transaction of null and we are 
            // required to send an empty transaction payload to the server.
 
            PropagateTransactionCookie(null);

            _isEnlistedInTransaction = false;
            EnlistedTransaction = null; // Tell the base class about our enlistment 

            if (Bid.AdvancedOn) { 
                Bid.Trace(" %d#, unenlisted.\n", base.ObjectID); 
            }
 
            // The EnlistTransaction above will return an TransactionEnded event,
            // which causes the TdsParser or SmiEventSink should to clear the
            // current transaction.
            // 
            // In either case, when we're working with a Yukon or newer server
            // we better not have a current transaction at this point. 
 
            Debug.Assert(!IsYukonOrNewer || null == CurrentTransaction, "unenlisted transaction with non-null current transaction?");   // verify it!
        } 

        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. 

            SNIHandle bestEffortCleanupTarget = null;
            RuntimeHelpers.PrepareConstrainedRegions();
            try { 
#if DEBUG
                object initialReliabilitySlotValue = Thread.GetData(TdsParser.ReliabilitySlot); 
 
                RuntimeHelpers.PrepareConstrainedRegions();
                try { 
                    Thread.SetData(TdsParser.ReliabilitySlot, true);
#endif //DEBUG
                    bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection);
                    Enlist(transaction); 
#if DEBUG
                } 
                finally { 
                    Thread.SetData(TdsParser.ReliabilitySlot, initialReliabilitySlotValue);
                } 
#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;
            } 
        } 

        abstract internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest); 

        internal SqlDataReader FindLiveReader(SqlCommand command) {
            SqlDataReader reader = null;
            SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection; 
            if (null != referenceCollection) {
                reader =  referenceCollection.FindLiveReader(command); 
            } 
            return reader;
        } 

        static internal SNIHandle GetBestEffortCleanupTarget(SqlConnection connection) {
            if (null != connection) {
                SqlInternalConnectionTds innerConnection = (connection.InnerConnection as SqlInternalConnectionTds); 
                if (null != innerConnection) {
                    TdsParser parser = innerConnection.Parser; 
                    if (parser != null) { 
                        return parser.GetBestEffortCleanupTarget();
                    } 
                }
            }

            return null; 
        }
 
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 
        static internal void BestEffortCleanup(SNIHandle target) {
            if (null != target) { 
                target.Dispose();
            }
        }
 
        abstract protected byte[] GetDTCAddress();
 
        static private byte[] GetTransactionCookie(SysTx.Transaction transaction, byte[] whereAbouts) { 
            byte[] transactionCookie = null;
            if (null != transaction) { 
                transactionCookie = SysTx.TransactionInterop.GetExportCookie(transaction, whereAbouts);
            }
            return transactionCookie;
        } 

        virtual protected void InternalDeactivate() { 
        } 

        internal void OnError(SqlException exception, bool breakConnection) { 
            if (breakConnection)
                DoomThisConnection();

            if (null != Connection) { 
                Connection.OnError(exception, breakConnection);
            } 
            else if (exception.Class >= TdsEnums.MIN_ERROR_CLASS) { 
                // It is an error, and should be thrown.  Class of TdsEnums.MIN_ERROR_CLASS
                // or above is an error, below TdsEnums.MIN_ERROR_CLASS denotes an info message. 
                throw exception;
            }
        }
 
        abstract protected void PropagateTransactionCookie(byte[] transactionCookie);
 
        abstract internal void RemovePreparedCommand(SqlCommand cmd); 

        abstract internal void ValidateConnectionForExecute(SqlCommand command); 

        // This validation step MUST be done after locking the connection to guarantee we don't
        //  accidentally execute after the transaction has completed on a different thread, causing us to unwittingly
        //  execute in auto-commit mode. 
        internal void ValidateTransaction() {
            if (RequireExplicitTransactionUnbind) { 
 
                // If we have a system transaction, check that transaction is active and is the current transaction
                if (null != InternalEnlistedTransaction) { 
                    SysTx.Transaction currentTran = SysTx.Transaction.Current;
                    if (SysTx.TransactionStatus.Active != InternalEnlistedTransaction.TransactionInformation.Status ||
                            null == currentTran ||
                            !InternalEnlistedTransaction.Equals(currentTran)) { 
                        throw ADP.TransactionConnectionMismatch();
                    } 
                } 
            }
        } 


    }
} 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK