Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data / System / Data / SqlClient / SqlInternalConnection.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="SqlInternalConnection.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8
9 namespace System.Data.SqlClient
10 {
11     using System;
12     using System.Collections.Generic;
13     using System.Data;
14     using System.Data.Common;
15     using System.Data.ProviderBase;
16     using System.Diagnostics;
17     using System.Globalization;
18     using System.Reflection;
19     using System.Runtime.CompilerServices;
20     using System.Runtime.ConstrainedExecution;
21     using System.Runtime.InteropServices;
22     using System.Security;
23     using System.Security.Permissions;
24     using System.Text;
25     using System.Threading;
26     using SysTx = System.Transactions;
27
28     abstract internal class SqlInternalConnection : DbConnectionInternal {
29         private readonly SqlConnectionString _connectionOptions;
30         private bool                         _isEnlistedInTransaction; // is the server-side connection enlisted? true while we're enlisted, reset only after we send a null...
31         private byte[]                       _promotedDTCToken;        // token returned by the server when we promote transaction
32         private byte[]                       _whereAbouts;             // cache the whereabouts (DTC Address) for exporting
33         
34         private bool                         _isGlobalTransaction = false; // Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction)
35         private bool                         _isGlobalTransactionEnabledForServer = false; // Whether Global Transactions are enabled for this Azure SQL DB Server
36         private static readonly Guid         _globalTransactionTMID = new Guid("1c742caf-6680-40ea-9c26-6b6846079764"); // ID of the Non-MSDTC, Azure SQL DB Transaction Manager
37         
38         // if connection is not open: null
39         // if connection is open: currently active database
40         internal string CurrentDatabase { get; set; }
41
42         // if connection is not open yet, CurrentDataSource is null
43         // if connection is open:
44         // * for regular connections, it is set to Data Source value from connection string
45         // * for connections with FailoverPartner, it is set to the FailoverPartner value from connection string if the connection was opened to it.
46         internal string CurrentDataSource { get; set; }
47
48         // the delegated (or promoted) transaction we're responsible for.
49         internal SqlDelegatedTransaction DelegatedTransaction { get; set; }
50
51         internal enum TransactionRequest {
52             Begin,
53             Promote,
54             Commit,
55             Rollback,
56             IfRollback,
57             Save
58         };
59
60         internal SqlInternalConnection(SqlConnectionString connectionOptions) : base() {
61             Debug.Assert(null != connectionOptions, "null connectionOptions?");
62             _connectionOptions = connectionOptions;
63         }
64
65         internal SqlConnection Connection {
66             get {
67                 return (SqlConnection)Owner;
68             }
69         }
70
71         internal SqlConnectionString ConnectionOptions {
72             get {
73                 return _connectionOptions;
74             }
75         }
76
77         abstract internal SqlInternalTransaction CurrentTransaction {
78             get;
79         }
80
81         // SQLBU 415870
82         //  Get the internal transaction that should be hooked to a new outer transaction
83         //  during a BeginTransaction API call.  In some cases (i.e. connection is going to 
84         //  be reset), CurrentTransaction should not be hooked up this way.
85         virtual internal SqlInternalTransaction AvailableInternalTransaction {
86             get {
87                 return CurrentTransaction;
88             }
89         }
90
91         abstract internal SqlInternalTransaction PendingTransaction {
92             get;
93         }
94
95         override protected internal bool IsNonPoolableTransactionRoot {
96             get {
97                 return IsTransactionRoot;  // default behavior is that root transactions are NOT poolable.  Subclasses may override.
98             }
99         }
100
101         override internal bool IsTransactionRoot {
102             get {
103                 var delegatedTransaction = DelegatedTransaction;
104                 return ((null != delegatedTransaction) && (delegatedTransaction.IsActive));
105             }
106         }
107
108         internal bool HasLocalTransaction {
109             get {
110                 SqlInternalTransaction currentTransaction = CurrentTransaction;
111                 bool result = (null != currentTransaction && currentTransaction.IsLocal);
112                 return result;
113             }
114         }
115
116         internal bool HasLocalTransactionFromAPI {
117             get {
118                 SqlInternalTransaction currentTransaction = CurrentTransaction;
119                 bool result = (null != currentTransaction && currentTransaction.HasParentTransaction);
120                 return result;
121             }
122         }
123
124         internal bool IsEnlistedInTransaction {
125             get {
126                 return _isEnlistedInTransaction;
127             }
128         }
129
130         abstract internal bool IsLockedForBulkCopy {
131             get;            
132         }
133
134         abstract internal bool IsShiloh {
135             get;
136         }
137
138         abstract internal bool IsYukonOrNewer {
139             get;
140         }
141
142         abstract internal bool IsKatmaiOrNewer {
143             get;
144         }
145
146         internal byte[] PromotedDTCToken {
147             get {
148                 return _promotedDTCToken;
149             }
150             set {
151                 _promotedDTCToken = value;
152             }
153         }
154
155         internal bool IsGlobalTransaction {
156             get {
157                 return _isGlobalTransaction;
158             }
159             set {
160                 _isGlobalTransaction = value;
161             }
162         }
163
164         internal bool IsGlobalTransactionsEnabledForServer {
165             get {
166                 return _isGlobalTransactionEnabledForServer;
167             }
168             set {
169                 _isGlobalTransactionEnabledForServer = value;
170             }
171         }
172
173         override public DbTransaction BeginTransaction(IsolationLevel iso) {
174             return BeginSqlTransaction(iso, null, false);
175         }
176
177         virtual internal SqlTransaction BeginSqlTransaction(IsolationLevel iso, string transactionName, bool shouldReconnect) {
178             SqlStatistics statistics = null;
179             TdsParser bestEffortCleanupTarget = null;
180             RuntimeHelpers.PrepareConstrainedRegions();
181             try {
182 #if DEBUG
183                 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
184
185                 RuntimeHelpers.PrepareConstrainedRegions();
186                 try {
187                     tdsReliabilitySection.Start();
188 #else
189                 {
190 #endif //DEBUG
191                     bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection);
192                     statistics = SqlStatistics.StartTimer(Connection.Statistics);
193
194                     SqlConnection.ExecutePermission.Demand(); // MDAC 81476
195
196                     ValidateConnectionForExecute(null);
197
198                     if (HasLocalTransactionFromAPI)
199                         throw ADP.ParallelTransactionsNotSupported(Connection);
200
201                     if (iso == IsolationLevel.Unspecified) {
202                         iso = IsolationLevel.ReadCommitted; // Default to ReadCommitted if unspecified.
203                     }
204
205                     SqlTransaction transaction = new SqlTransaction(this, Connection, iso, AvailableInternalTransaction);
206                     transaction.InternalTransaction.RestoreBrokenConnection = shouldReconnect;
207                     ExecuteTransaction(TransactionRequest.Begin, transactionName, iso, transaction.InternalTransaction, false);
208                     transaction.InternalTransaction.RestoreBrokenConnection = false;
209                     return transaction;
210                 }
211 #if DEBUG
212                 finally {
213                     tdsReliabilitySection.Stop();
214                 }
215 #endif //DEBUG
216             }
217             catch (System.OutOfMemoryException e) {
218                 Connection.Abort(e);
219                 throw;
220             }
221             catch (System.StackOverflowException e) {
222                 Connection.Abort(e);
223                 throw;
224             }
225             catch (System.Threading.ThreadAbortException e) {
226                 Connection.Abort(e);
227                 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
228                 throw;
229             }
230             finally {
231                 SqlStatistics.StopTimer(statistics);
232             }
233         }
234
235         override public void ChangeDatabase(string database) {
236             SqlConnection.ExecutePermission.Demand(); // MDAC 80961
237
238             if (ADP.IsEmpty(database)) {
239                 throw ADP.EmptyDatabaseName();
240             }
241
242             ValidateConnectionForExecute(null); // 
243
244             ChangeDatabaseInternal(database);  // do the real work...
245         }
246
247         abstract protected void ChangeDatabaseInternal(string database);
248         
249         override protected void CleanupTransactionOnCompletion(SysTx.Transaction transaction) {
250             // Note: unlocked, potentially multi-threaded code, so pull delegate to local to 
251             //  ensure it doesn't change between test and call.
252             SqlDelegatedTransaction delegatedTransaction = DelegatedTransaction;
253             if (null != delegatedTransaction) {
254                 delegatedTransaction.TransactionEnded(transaction);
255             }
256         }
257
258         override protected DbReferenceCollection CreateReferenceCollection() {
259             return new SqlReferenceCollection();
260         }
261
262         override protected void Deactivate() {
263             if (Bid.AdvancedOn) {
264                 Bid.Trace("<sc.SqlInternalConnection.Deactivate|ADV> %d# deactivating\n", base.ObjectID);
265             }
266             TdsParser bestEffortCleanupTarget = null;
267             RuntimeHelpers.PrepareConstrainedRegions();
268             try {
269
270 #if DEBUG
271                 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
272
273                 RuntimeHelpers.PrepareConstrainedRegions();
274                 try {
275                     tdsReliabilitySection.Start();
276 #else
277                 {
278 #endif //DEBUG
279                     bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection);
280                     SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection;
281                     if (null != referenceCollection) {
282                         referenceCollection.Deactivate();
283                     }
284
285                     // Invoke subclass-specific deactivation logic
286                     InternalDeactivate();
287                 }
288 #if DEBUG
289                 finally {
290                     tdsReliabilitySection.Stop();
291                 }
292 #endif //DEBUG
293             }
294             catch (System.OutOfMemoryException) {
295                 DoomThisConnection();
296                 throw;
297             }
298             catch (System.StackOverflowException) {
299                 DoomThisConnection();
300                 throw;
301             }
302             catch (System.Threading.ThreadAbortException) {
303                 DoomThisConnection();
304                 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
305                 throw;
306             }
307             catch (Exception e) {
308                 // 
309                 if (!ADP.IsCatchableExceptionType(e)) {
310                     throw;
311                 }
312
313                 // if an exception occurred, the inner connection will be
314                 // marked as unusable and destroyed upon returning to the
315                 // pool
316                 DoomThisConnection();
317
318                 ADP.TraceExceptionWithoutRethrow(e);
319             }
320         }
321        
322         abstract internal void DisconnectTransaction(SqlInternalTransaction internalTransaction);
323
324         override public void Dispose() {
325             _whereAbouts = null;
326             base.Dispose();
327         }
328
329         protected void Enlist(SysTx.Transaction tx) {
330             // This method should not be called while the connection has a 
331             // reference to an active delegated transaction.
332             // Manual enlistment via SqlConnection.EnlistTransaction
333             // should catch this case and throw an exception.
334             //
335             // Automatic enlistment isn't possible because 
336             // Sys.Tx keeps the connection alive until the transaction is completed.
337             Debug.Assert (!IsNonPoolableTransactionRoot, "cannot defect an active delegated transaction!");  // potential race condition, but it's an assert
338
339             if (null == tx) {
340                 if (IsEnlistedInTransaction)
341                 {
342                     EnlistNull();
343                 }
344                 else 
345                 {
346                     // When IsEnlistedInTransaction is false, it means we are in one of two states:
347                     // 1. EnlistTransaction is null, so the connection is truly not enlisted in a transaction, or
348                     // 2. Connection is enlisted in a SqlDelegatedTransaction.
349                     //
350                     // For #2, we have to consider whether or not the delegated transaction is active.
351                     // If it is not active, we allow the enlistment in the NULL transaction.
352                     //
353                     // If it is active, technically this is an error.
354                     // However, no exception is thrown as this was the precedent (and this case is silently ignored, no error, but no enlistment either).
355                     // There are two mitigations for this:
356                     // 1. SqlConnection.EnlistTransaction checks that the enlisted transaction has completed before allowing a different enlistment.
357                     // 2. For debug builds, the assert at the beginning of this method checks for an enlistment in an active delegated transaction.
358                     SysTx.Transaction enlistedTransaction = EnlistedTransaction;
359                     if (enlistedTransaction != null && enlistedTransaction.TransactionInformation.Status != SysTx.TransactionStatus.Active)
360                     {
361                         EnlistNull();
362                     }
363                 }
364             }
365             // Only enlist if it's different...
366             else if (!tx.Equals(EnlistedTransaction)) { // WebData 20000024 - Must use Equals, not !=
367                 EnlistNonNull(tx);
368             }
369         }
370
371         private void EnlistNonNull(SysTx.Transaction tx) {
372             Debug.Assert(null != tx, "null transaction?");
373
374             if (Bid.AdvancedOn) {
375                 Bid.Trace("<sc.SqlInternalConnection.EnlistNonNull|ADV> %d#, transaction %d#.\n", base.ObjectID, tx.GetHashCode());
376             }
377             
378             bool hasDelegatedTransaction = false;
379
380             if (IsYukonOrNewer) {
381                 if (Bid.AdvancedOn) {
382                     Bid.Trace("<sc.SqlInternalConnection.EnlistNonNull|ADV> %d#, attempting to delegate\n", base.ObjectID);
383                 }
384
385                 // Promotable transactions are only supported on Yukon
386                 // servers or newer.
387                 SqlDelegatedTransaction delegatedTransaction = new SqlDelegatedTransaction(this, tx);
388                 
389                 try {
390                     // NOTE: System.Transactions claims to resolve all
391                     // potential race conditions between multiple delegate
392                     // requests of the same transaction to different
393                     // connections in their code, such that only one
394                     // attempt to delegate will succeed.
395
396                     // NOTE: PromotableSinglePhaseEnlist will eventually
397                     // make a round trip to the server; doing this inside
398                     // a lock is not the best choice.  We presume that you
399                     // aren't trying to enlist concurrently on two threads
400                     // and leave it at that -- We don't claim any thread
401                     // safety with regard to multiple concurrent requests
402                     // to enlist the same connection in different
403                     // transactions, which is good, because we don't have
404                     // it anyway.
405
406                     // PromotableSinglePhaseEnlist may not actually promote
407                     // the transaction when it is already delegated (this is
408                     // the way they resolve the race condition when two
409                     // threads attempt to delegate the same Lightweight
410                     // Transaction)  In that case, we can safely ignore
411                     // our delegated transaction, and proceed to enlist
412                     // in the promoted one.
413
414                     // NOTE: Global Transactions is an Azure SQL DB only 
415                     // feature where the Transaction Manager (TM) is not
416                     // MS-DTC. Sys.Tx added APIs to support Non MS-DTC
417                     // promoter types/TM in .NET 4.6.1. Following directions
418                     // from .NETFX shiproom, to avoid a "hard-dependency"
419                     // (compile time) on Sys.Tx, we use reflection to invoke
420                     // the new APIs. Further, the _isGlobalTransaction flag
421                     // indicates that this is an Azure SQL DB Transaction
422                     // that could be promoted to a Global Transaction (it's
423                     // always false for on-prem Sql Server). The Promote()
424                     // call in SqlDelegatedTransaction makes sure that the
425                     // right Sys.Tx.dll is loaded and that Global Transactions
426                     // are actually allowed for this Azure SQL DB.
427
428                     if (_isGlobalTransaction) {
429                         if (SysTxForGlobalTransactions.EnlistPromotableSinglePhase == null) {
430                             // This could be a local Azure SQL DB transaction. 
431                             hasDelegatedTransaction = tx.EnlistPromotableSinglePhase(delegatedTransaction);
432                         }
433                         else {
434                             hasDelegatedTransaction = (bool)SysTxForGlobalTransactions.EnlistPromotableSinglePhase.Invoke(tx, new object[] { delegatedTransaction, _globalTransactionTMID });
435                         }
436                     }
437                     else {
438                         // This is an MS-DTC distributed transaction
439                         hasDelegatedTransaction = tx.EnlistPromotableSinglePhase(delegatedTransaction);
440                     }
441
442                     if (hasDelegatedTransaction) {
443
444                         this.DelegatedTransaction = delegatedTransaction;
445
446                         if (Bid.AdvancedOn) {
447                             long transactionId = SqlInternalTransaction.NullTransactionId;
448                             int transactionObjectID = 0; 
449                             if (null != CurrentTransaction) {
450                                 transactionId = CurrentTransaction.TransactionId;
451                                 transactionObjectID = CurrentTransaction.ObjectID;
452                             }
453                             Bid.Trace("<sc.SqlInternalConnection.EnlistNonNull|ADV> %d#, delegated to transaction %d# with transactionId=0x%I64x\n", base.ObjectID, transactionObjectID, transactionId);
454                         }
455                     }
456                 }
457                 catch (SqlException e) {
458                     // we do not want to eat the error if it is a fatal one
459                     if (e.Class >= TdsEnums.FATAL_ERROR_CLASS) {
460                         throw;
461                     }
462
463                     // if the parser is null or its state is not openloggedin, the connection is no longer good.
464                     SqlInternalConnectionTds tdsConnection = this as SqlInternalConnectionTds;
465                     if (tdsConnection != null)
466                     {
467                         TdsParser parser = tdsConnection.Parser;
468                         if (parser == null || parser.State != TdsParserState.OpenLoggedIn)
469                         {
470                             throw;
471                         }
472                     }
473
474                     ADP.TraceExceptionWithoutRethrow(e);
475
476                     // In this case, SqlDelegatedTransaction.Initialize
477                     // failed and we don't necessarily want to reject
478                     // things -- there may have been a legitimate reason
479                     // for the failure.
480                 }
481             }
482             
483             if (!hasDelegatedTransaction) {
484                 if (Bid.AdvancedOn) {
485                     Bid.Trace("<sc.SqlInternalConnection.EnlistNonNull|ADV> %d#, delegation not possible, enlisting.\n", base.ObjectID);
486                 }
487
488                 byte[] cookie = null;
489
490                 if (_isGlobalTransaction) {
491                     if (SysTxForGlobalTransactions.GetPromotedToken == null) {
492                         throw SQL.UnsupportedSysTxForGlobalTransactions();
493                     }
494
495                     cookie = (byte[])SysTxForGlobalTransactions.GetPromotedToken.Invoke(tx, null);
496                 }
497                 else {
498                     if (null == _whereAbouts) {
499                         byte[] dtcAddress = GetDTCAddress();
500
501                         if (null == dtcAddress) {
502                             throw SQL.CannotGetDTCAddress();
503                         }
504                         _whereAbouts = dtcAddress;
505                     }
506
507                     cookie = GetTransactionCookie(tx, _whereAbouts);
508                 }
509
510                 // send cookie to server to finish enlistment
511                 PropagateTransactionCookie(cookie);
512                 
513                 _isEnlistedInTransaction = true;
514
515                 if (Bid.AdvancedOn) {
516                     long transactionId = SqlInternalTransaction.NullTransactionId;
517                     int transactionObjectID = 0; 
518                     if (null != CurrentTransaction) {
519                         transactionId = CurrentTransaction.TransactionId;
520                         transactionObjectID = CurrentTransaction.ObjectID;
521                     }
522                     Bid.Trace("<sc.SqlInternalConnection.EnlistNonNull|ADV> %d#, enlisted with transaction %d# with transactionId=0x%I64x\n", base.ObjectID, transactionObjectID, transactionId);
523                 }
524             }
525
526             EnlistedTransaction = tx; // Tell the base class about our enlistment
527
528
529             // If we're on a Yukon or newer server, and we we delegate the 
530             // transaction successfully, we will have done a begin transaction, 
531             // which produces a transaction id that we should execute all requests
532             // on.  The TdsParser or SmiEventSink will store this information as
533             // the current transaction.
534             // 
535             // Likewise, propagating a transaction to a Yukon or newer server will
536             // produce a transaction id that The TdsParser or SmiEventSink will 
537             // store as the current transaction.
538             //
539             // In either case, when we're working with a Yukon or newer server 
540             // we better have a current transaction by now.
541
542             Debug.Assert(!IsYukonOrNewer || null != CurrentTransaction, "delegated/enlisted transaction with null current transaction?");
543         }
544
545         internal void EnlistNull() {
546             if (Bid.AdvancedOn) {
547                 Bid.Trace("<sc.SqlInternalConnection.EnlistNull|ADV> %d#, unenlisting.\n", base.ObjectID);
548             }
549
550             // We were in a transaction, but now we are not - so send
551             // message to server with empty transaction - confirmed proper
552             // behavior from Sameet Agarwal
553             //
554             // The connection pooler maintains separate pools for enlisted
555             // transactions, and only when that transaction is committed or
556             // rolled back will those connections be taken from that
557             // separate pool and returned to the general pool of connections
558             // that are not affiliated with any transactions.  When this
559             // occurs, we will have a new transaction of null and we are
560             // required to send an empty transaction payload to the server.
561
562             PropagateTransactionCookie(null);
563
564             _isEnlistedInTransaction = false;
565             EnlistedTransaction = null; // Tell the base class about our enlistment
566
567             if (Bid.AdvancedOn) {
568                 Bid.Trace("<sc.SqlInternalConnection.EnlistNull|ADV> %d#, unenlisted.\n", base.ObjectID);
569             }
570
571             // The EnlistTransaction above will return an TransactionEnded event, 
572             // which causes the TdsParser or SmiEventSink should to clear the
573             // current transaction.
574             //
575             // In either case, when we're working with a Yukon or newer server 
576             // we better not have a current transaction at this point.
577
578             Debug.Assert(!IsYukonOrNewer || null == CurrentTransaction, "unenlisted transaction with non-null current transaction?");   // verify it!
579         }
580         
581         override public void EnlistTransaction(SysTx.Transaction transaction) {
582             SqlConnection.VerifyExecutePermission();
583
584             ValidateConnectionForExecute(null);
585
586             // If a connection has a local transaction outstanding and you try
587             // to enlist in a DTC transaction, SQL Server will rollback the
588             // local transaction and then do the enlist (7.0 and 2000).  So, if
589             // the user tries to do this, throw.
590             if (HasLocalTransaction) {
591                 throw ADP.LocalTransactionPresent();
592             }
593
594             if (null != transaction && transaction.Equals(EnlistedTransaction)) {
595                 // No-op if this is the current transaction
596                 return;
597             }
598
599             // If a connection is already enlisted in a DTC transaction and you
600             // try to enlist in another one, in 7.0 the existing DTC transaction
601             // would roll back and then the connection would enlist in the new
602             // one. In SQL 2000 & Yukon, when you enlist in a DTC transaction
603             // while the connection is already enlisted in a DTC transaction,
604             // the connection simply switches enlistments.  Regardless, simply
605             // enlist in the user specified distributed transaction.  This
606             // behavior matches OLEDB and ODBC.
607
608             TdsParser bestEffortCleanupTarget = null;
609             RuntimeHelpers.PrepareConstrainedRegions();
610             try {
611 #if DEBUG
612                 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
613
614                 RuntimeHelpers.PrepareConstrainedRegions();
615                 try {
616                     tdsReliabilitySection.Start();
617 #else
618                 {
619 #endif //DEBUG
620                     bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection);
621                     Enlist(transaction);
622                 }
623 #if DEBUG
624                 finally {
625                     tdsReliabilitySection.Stop();
626                 }
627 #endif //DEBUG
628             }
629             catch (System.OutOfMemoryException e) {
630                 Connection.Abort(e);
631                 throw;
632             }
633             catch (System.StackOverflowException e) {
634                 Connection.Abort(e);
635                 throw;
636             }
637             catch (System.Threading.ThreadAbortException e) {
638                 Connection.Abort(e);
639                 SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
640                 throw;
641             }
642         }
643
644         abstract internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest);
645
646         internal SqlDataReader FindLiveReader(SqlCommand command) {
647             SqlDataReader reader = null;
648             SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection;
649             if (null != referenceCollection) {
650                 reader =  referenceCollection.FindLiveReader(command);
651             }
652             return reader;
653         }
654
655         internal SqlCommand FindLiveCommand(TdsParserStateObject stateObj) {
656             SqlCommand command = null;
657             SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection;
658             if (null != referenceCollection) {
659                 command =  referenceCollection.FindLiveCommand(stateObj);
660             }
661             return command;
662         }
663
664         static internal TdsParser GetBestEffortCleanupTarget(SqlConnection connection) {
665             if (null != connection) {
666                 SqlInternalConnectionTds innerConnection = (connection.InnerConnection as SqlInternalConnectionTds);
667                 if (null != innerConnection) {
668                     return innerConnection.Parser;
669                 }
670             }
671
672             return null;
673         }
674
675         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
676         static internal void BestEffortCleanup(TdsParser target) {
677             if (null != target) {
678                 target.BestEffortCleanup();
679             }
680         }
681
682         abstract protected byte[] GetDTCAddress();
683
684         static private byte[] GetTransactionCookie(SysTx.Transaction transaction, byte[] whereAbouts) {
685             byte[] transactionCookie = null;
686             if (null != transaction) {
687                 transactionCookie = SysTx.TransactionInterop.GetExportCookie(transaction, whereAbouts);
688             }
689             return transactionCookie;
690         }
691
692         virtual protected void InternalDeactivate() {
693         }
694
695         // If wrapCloseInAction is defined, then the action it defines will be run with the connection close action passed in as a parameter
696         // The close action also supports being run asynchronously
697         internal void OnError(SqlException exception, bool breakConnection, Action<Action> wrapCloseInAction = null) {
698             if (breakConnection) {
699                 DoomThisConnection();
700             }
701
702             var connection = Connection;
703             if (null != connection) {
704                 connection.OnError(exception, breakConnection, wrapCloseInAction);
705             }
706             else if (exception.Class >= TdsEnums.MIN_ERROR_CLASS) {
707                 // It is an error, and should be thrown.  Class of TdsEnums.MIN_ERROR_CLASS
708                 // or above is an error, below TdsEnums.MIN_ERROR_CLASS denotes an info message.
709                 throw exception;
710             }
711         }
712
713         abstract protected void PropagateTransactionCookie(byte[] transactionCookie);
714         
715         abstract internal void ValidateConnectionForExecute(SqlCommand command);
716     }
717 }