SqlInternalConnection.cs 31 KB

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