diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index 32b9e76c98..36100440ce 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -210,6 +210,9 @@
Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs
+
+ Microsoft\Data\SqlClient\Connection\CachedContexts.cs
+
Microsoft\Data\SqlClient\Connection\ServerInfo.cs
@@ -219,6 +222,9 @@
Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs
+
+ Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs
+
Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs
@@ -699,12 +705,6 @@
Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs
-
- Microsoft\Data\SqlClient\SqlInternalConnection.cs
-
-
- Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs
-
Microsoft\Data\SqlClient\SqlInternalTransaction.cs
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index 4bfe45ee72..6b6d5eb35a 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -470,6 +470,9 @@
Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs
+
+ Microsoft\Data\SqlClient\Connection\CachedContexts.cs
+
Microsoft\Data\SqlClient\Connection\ServerInfo.cs
@@ -479,6 +482,9 @@
Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs
+
+ Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs
+
Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs
@@ -854,12 +860,6 @@
Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs
-
- Microsoft\Data\SqlClient\SqlInternalConnection.cs
-
-
- Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs
-
Microsoft\Data\SqlClient\SqlInternalTransaction.cs
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index 46375fbf20..321befedff 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -25,6 +25,7 @@
using Microsoft.SqlServer.Server;
using System.Security.Authentication;
using System.Collections.Generic;
+using Microsoft.Data.SqlClient.Connection;
#if NETFRAMEWORK
using System.Reflection;
@@ -455,7 +456,11 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int
internal static ArgumentException MustBeReadOnly(string argumentName) => Argument(StringsHelper.GetString(Strings.ADP_MustBeReadOnly, argumentName));
- internal static Exception CreateSqlException(MsalException msalException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username)
+ internal static Exception CreateSqlException(
+ MsalException msalException,
+ SqlConnectionString connectionOptions,
+ SqlConnectionInternal sender,
+ string username)
{
// Error[0]
SqlErrorCollection sqlErs = new();
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/CachedContexts.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/CachedContexts.cs
new file mode 100644
index 0000000000..d7a6073d13
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/CachedContexts.cs
@@ -0,0 +1,180 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+
+#nullable enable
+
+namespace Microsoft.Data.SqlClient.Connection
+{
+ ///
+ /// Provides thread-safe caching and sharing of asynchronous call contexts between objects
+ /// within a single SQL connection context.
+ ///
+ ///
+ /// This internal class manages reusable context objects for various asynchronous operations
+ /// such as ExecuteNonQueryAsync, ExecuteReaderAsync, etc, performed on a connection, enabling
+ /// efficient reuse and reducing allocations.
+ ///
+ /// Thread safety is ensured via interlocked operations, allowing concurrent access and
+ /// updates without explicit locking. All accessors and mutators are designed to be safe for
+ /// use by multiple threads.
+ ///
+ /// Intended for internal use by connection management infrastructure.
+ ///
+ internal class CachedContexts
+ {
+ #region Fields
+
+ ///
+ /// Stores reusable context for ExecuteNonQueryAsync invocations.
+ ///
+ private SqlCommand.ExecuteNonQueryAsyncCallContext? _commandExecuteNonQueryAsyncContext;
+
+ ///
+ /// Stores reusable context for ExecuteReaderAsync invocations.
+ ///
+ private SqlCommand.ExecuteReaderAsyncCallContext? _commandExecuteReaderAsyncContext;
+
+ ///
+ /// Stores reusable context for ExecuteXmlReaderAsync invocations.
+ ///
+ private SqlCommand.ExecuteXmlReaderAsyncCallContext? _commandExecuteXmlReaderAsyncContext;
+
+ ///
+ /// Stores reusable context for IsDBNullAsync invocations.
+ ///
+ private SqlDataReader.IsDBNullAsyncCallContext? _dataReaderIsDbNullContext;
+
+ ///
+ /// Stores reusable context for ReadAsync invocations.
+ ///
+ private SqlDataReader.ReadAsyncCallContext? _dataReaderReadAsyncContext;
+
+ ///
+ /// Stores a data reader snapshot.
+ ///
+ private SqlDataReader.Snapshot? _dataReaderSnapshot;
+
+ #endregion
+
+ #region Access Methods
+
+ ///
+ /// Removes and returns the cached ExecuteNonQueryAsync context.
+ ///
+ /// The previously cached context or null when empty.
+ internal SqlCommand.ExecuteNonQueryAsyncCallContext? TakeCommandExecuteNonQueryAsyncContext() =>
+ Interlocked.Exchange(ref _commandExecuteNonQueryAsyncContext, null);
+
+ ///
+ /// Removes and returns the cached ExecuteReaderAsync context.
+ ///
+ /// The previously cached context or null when empty.
+ internal SqlCommand.ExecuteReaderAsyncCallContext? TakeCommandExecuteReaderAsyncContext() =>
+ Interlocked.Exchange(ref _commandExecuteReaderAsyncContext, null);
+
+ ///
+ /// Removes and returns the cached ExecuteXmlReaderAsync context.
+ ///
+ /// The previously cached context or null when empty.
+ internal SqlCommand.ExecuteXmlReaderAsyncCallContext? TakeCommandExecuteXmlReaderAsyncContext() =>
+ Interlocked.Exchange(ref _commandExecuteXmlReaderAsyncContext, null);
+
+ ///
+ /// Removes and returns the cached ReadAsync context.
+ ///
+ /// The previously cached context or null when empty.
+ internal SqlDataReader.ReadAsyncCallContext? TakeDataReaderReadAsyncContext() =>
+ Interlocked.Exchange(ref _dataReaderReadAsyncContext, null);
+
+ ///
+ /// Removes and returns the cached IsDBNullAsync context.
+ ///
+ /// The previously cached context or null when empty.
+ internal SqlDataReader.IsDBNullAsyncCallContext? TakeDataReaderIsDbNullContext() =>
+ Interlocked.Exchange(ref _dataReaderIsDbNullContext, null);
+
+ ///
+ /// Removes and returns the cached data reader snapshot.
+ ///
+ /// The previously cached snapshot or null when empty.
+ internal SqlDataReader.Snapshot? TakeDataReaderSnapshot() =>
+ Interlocked.Exchange(ref _dataReaderSnapshot, null);
+
+ ///
+ /// Attempts to cache the provided ExecuteNonQueryAsync context.
+ ///
+ /// Context instance to store.
+ ///
+ /// True when the context is cached; false if an existing value is preserved.
+ ///
+ internal bool TrySetCommandExecuteNonQueryAsyncContext(SqlCommand.ExecuteNonQueryAsyncCallContext value) =>
+ TrySetContext(value, ref _commandExecuteNonQueryAsyncContext);
+
+ ///
+ /// Attempts to cache the provided ExecuteReaderAsync context.
+ ///
+ /// Context instance to store.
+ ///
+ /// True when the context is cached; false if an existing value is preserved.
+ ///
+ internal bool TrySetCommandExecuteReaderAsyncContext(SqlCommand.ExecuteReaderAsyncCallContext value) =>
+ TrySetContext(value, ref _commandExecuteReaderAsyncContext);
+
+ ///
+ /// Attempts to cache the provided ExecuteXmlReaderAsync context.
+ ///
+ /// Context instance to store.
+ ///
+ /// True when the context is cached; false if an existing value is preserved.
+ ///
+ internal bool TrySetCommandExecuteXmlReaderAsyncContext(SqlCommand.ExecuteXmlReaderAsyncCallContext value) =>
+ TrySetContext(value, ref _commandExecuteXmlReaderAsyncContext);
+
+ ///
+ /// Attempts to cache the provided ReadAsync context.
+ ///
+ /// Context instance to store.
+ ///
+ /// True when the context is cached; false if an existing value is preserved.
+ ///
+ internal bool TrySetDataReaderReadAsyncContext(SqlDataReader.ReadAsyncCallContext value) =>
+ TrySetContext(value, ref _dataReaderReadAsyncContext);
+
+ ///
+ /// Attempts to cache the provided IsDBNullAsync context.
+ ///
+ /// Context instance to store.
+ ///
+ /// True when the context is cached; false if an existing value is preserved.
+ ///
+ internal bool TrySetDataReaderIsDbNullContext(SqlDataReader.IsDBNullAsyncCallContext value) =>
+ TrySetContext(value, ref _dataReaderIsDbNullContext);
+
+ ///
+ /// Attempts to cache the provided data reader snapshot context.
+ ///
+ /// Context instance to store.
+ ///
+ /// True when the snapshot is cached; false if an existing snapshot is preserved.
+ ///
+ internal bool TrySetDataReaderSnapshot(SqlDataReader.Snapshot value) =>
+ TrySetContext(value, ref _dataReaderSnapshot);
+
+ #endregion
+
+ private static bool TrySetContext(TContext value, ref TContext? location)
+ where TContext : class
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ return Interlocked.CompareExchange(ref location, value, null) is null;
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs
similarity index 86%
rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs
index 80864716a3..5567dac6d5 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs
@@ -20,10 +20,11 @@
using Microsoft.Data.SqlClient.Connection;
using Microsoft.Data.SqlClient.ConnectionPool;
using Microsoft.Identity.Client;
+using IsolationLevel = System.Data.IsolationLevel;
-namespace Microsoft.Data.SqlClient
+namespace Microsoft.Data.SqlClient.Connection
{
- internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable
+ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable
{
#region Constants
@@ -57,7 +58,14 @@ internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable
/// same.
///
// @TODO: Rename to match naming conventions (s_camelCase or PascalCase)
- private static readonly TimeSpan _dbAuthenticationContextUnLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 10, seconds: 00);
+ private static readonly TimeSpan _dbAuthenticationContextUnLockedRefreshTimeSpan =
+ new TimeSpan(hours: 0, minutes: 10, seconds: 00);
+
+ ///
+ /// ID of the Azure SQL DB Transaction Manager (Non-MSDTC)
+ ///
+ // @TODO: Rename to a match naming conventions (s_camelCase or PascalCase)
+ private static readonly Guid s_globalTransactionTMID = new("1C742CAF-6680-40EA-9C26-6B6846079764");
private static readonly HashSet s_transientErrors =
[
@@ -306,6 +314,12 @@ internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable
private readonly SqlConnectionTimeoutErrorInternal _timeoutErrorInternal;
+ ///
+ /// Cache the whereabouts (DTC Address) for exporting.
+ ///
+ // @TODO: This name ... doesn't make a whole lot of sense.
+ private byte[] _whereAbouts;
+
#endregion
#region Constructors
@@ -318,7 +332,7 @@ internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable
/// has been expanded (see SqlConnectionString.Expand)
///
// @TODO: We really really need simplify what we pass into this. All these optional parameters need to go!
- internal SqlInternalConnectionTds(
+ internal SqlConnectionInternal(
DbConnectionPoolIdentity identity,
SqlConnectionString connectionOptions,
SqlCredential credential,
@@ -332,8 +346,12 @@ internal SqlInternalConnectionTds(
string accessToken = null,
IDbConnectionPool pool = null,
Func> accessTokenCallback = null,
- SspiContextProvider sspiContextProvider = null) : base(connectionOptions)
+ SspiContextProvider sspiContextProvider = null)
{
+ Debug.Assert(connectionOptions is not null, "null connectionOptions");
+
+ ConnectionOptions = connectionOptions;
+
#if DEBUG
if (reconnectSessionData != null)
{
@@ -487,10 +505,10 @@ public override string ServerVersion
get => $"{_loginAck.majorVersion:00}.{(short)_loginAck.minorVersion:00}.{_loginAck.buildNum:0000}";
}
- internal override SqlInternalTransaction AvailableInternalTransaction
- {
- get => _parser._fResetConnection ? null : CurrentTransaction;
- }
+ ///
+ /// Gets the collection of async call contexts that belong to this connection.
+ ///
+ internal CachedContexts CachedContexts { get; private set; } = new CachedContexts();
// @TODO: Make auto-property
internal Guid ClientConnectionId
@@ -498,6 +516,32 @@ internal Guid ClientConnectionId
get => _clientConnectionId;
}
+ ///
+ /// A reference to the SqlConnection that owns this internal connection.
+ ///
+ internal SqlConnection Connection => (SqlConnection)Owner;
+
+ ///
+ /// The connection options to be used for this connection.
+ ///
+ internal SqlConnectionString ConnectionOptions { get; }
+
+ ///
+ /// The current database for this connection. Null if the connection is not open yet.
+ ///
+ internal string CurrentDatabase { get; private set; }
+
+ ///
+ /// The current data source for this connection.
+ ///
+ ///
+ /// If connection is not open yet, CurrentDataSource is null
+ /// If connection is open:
+ /// * for regular connections, it is set to the Data Source value from connection string
+ /// * for failover connections, it is set to the FailoverPartner value from the connection string
+ ///
+ internal string CurrentDataSource { get; set; }
+
internal SessionData CurrentSessionData
{
get
@@ -511,11 +555,37 @@ internal SessionData CurrentSessionData
}
}
- internal override SqlInternalTransaction CurrentTransaction
+ ///
+ /// The Transaction currently associated with this connection.
+ ///
+ internal SqlInternalTransaction CurrentTransaction
{
get => _parser.CurrentTransaction;
}
+ ///
+ /// The delegated (or promoted) transaction this connection is responsible for.
+ ///
+ internal SqlDelegatedTransaction DelegatedTransaction { get; set; }
+
+ ///
+ /// Whether this connection has a local (non-delegated) transaction.
+ ///
+ internal bool HasLocalTransaction
+ {
+ get => CurrentTransaction?.IsLocal == true;
+ }
+
+ ///
+ /// Whether this connection has a local transaction started from the API (i.e.,
+ /// SqlConnection.BeginTransaction) or had a TSQL transaction and later got wrapped by an
+ /// API transaction.
+ ///
+ internal bool HasLocalTransactionFromAPI
+ {
+ get => CurrentTransaction?.HasParentTransaction == true;
+ }
+
// @TODO: Make auto-property
internal DbConnectionPoolIdentity Identity
{
@@ -537,7 +607,7 @@ internal string InstanceName
get => _instanceName;
}
- internal override bool Is2008OrNewer
+ internal bool Is2008OrNewer
{
get => _parser.Is2008OrNewer;
}
@@ -553,7 +623,8 @@ internal override bool IsAccessTokenExpired
}
///
- /// Get or set if the control ring send redirect token and feature ext ack with true for DNSCaching
+ /// Get or set if the control ring send redirect token and feature ext ack with true for
+ /// DNSCaching.
///
/// @TODO: Make auto-property
internal bool IsDNSCachingBeforeRedirectSupported
@@ -562,7 +633,27 @@ internal bool IsDNSCachingBeforeRedirectSupported
set => _dnsCachingBeforeRedirect = value;
}
- internal override bool IsLockedForBulkCopy
+ ///
+ /// Indicates whether the connection is currently enlisted in a transaction.
+ ///
+ internal bool IsEnlistedInTransaction { get; private set; }
+
+ ///
+ /// Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction)
+ /// TODO: overlaps with IsGlobalTransactionsEnabledForServer, need to consolidate to avoid bugs
+ ///
+ internal bool IsGlobalTransaction { get; private set; }
+
+ ///
+ /// Whether Global Transactions are enabled. Only supported by Azure SQL. False if disabled
+ /// or connected to on-prem SQL Server.
+ ///
+ internal bool IsGlobalTransactionsEnabledForServer { get; private set; }
+
+ ///
+ /// Whether this connection is locked for bulk copy operations.
+ ///
+ internal bool IsLockedForBulkCopy
{
get => !_parser.MARSOn && _parser._physicalStateObj.BcpLock;
}
@@ -587,6 +678,14 @@ internal bool IsSQLDNSRetryEnabled
set => _SQLDNSRetryEnabled = value;
}
+ ///
+ /// Whether this connection is the root of a delegated or promoted transaction.
+ ///
+ internal override bool IsTransactionRoot
+ {
+ get => DelegatedTransaction?.IsActive == true;
+ }
+
// @TODO: Make auto-property
internal Guid OriginalClientConnectionId
{
@@ -605,7 +704,10 @@ internal TdsParser Parser
get => _parser;
}
- internal override SqlInternalTransaction PendingTransaction
+ ///
+ /// TODO: need to understand this property better
+ ///
+ internal SqlInternalTransaction PendingTransaction
{
get => _parser.PendingTransaction;
}
@@ -616,6 +718,11 @@ internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo
get => _poolGroupProviderInfo;
}
+ ///
+ /// A token returned by the server when we promote transaction.
+ ///
+ internal byte[] PromotedDtcToken { get; private set; }
+
// @TODO: Make auto-property
internal string RoutingDestination
{
@@ -697,10 +804,130 @@ private int accessTokenExpirationBufferTime
: ConnectionOptions.ConnectTimeout;
}
+ // 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.
+ // TODO: (mdaigle) need to understand this property better
+ private SqlInternalTransaction AvailableInternalTransaction
+ {
+ get => _parser._fResetConnection ? null : CurrentTransaction;
+ }
+
+ ///
+ /// Whether this connection is to an Azure SQL Database.
+ ///
+ // @TODO: Make private field.
+ private bool IsAzureSqlConnection { get; set; }
+
#endregion
#region Public and Internal Methods
+ public override DbTransaction BeginTransaction(IsolationLevel iso) =>
+ BeginSqlTransaction(iso, transactionName: null, shouldReconnect: false);
+
+ public override void ChangeDatabase(string database)
+ {
+ if (string.IsNullOrEmpty(database))
+ {
+ throw ADP.EmptyDatabaseName();
+ }
+
+ ValidateConnectionForExecute(null);
+
+ // MDAC 73598 - add brackets around database
+ database = SqlConnection.FixupDatabaseTransactionName(database); // @TODO: Should go to a utility method
+ Task executeTask = _parser.TdsExecuteSQLBatch(
+ $@"USE {database}",
+ ConnectionOptions.ConnectTimeout,
+ notificationRequest: null,
+ _parser._physicalStateObj,
+ sync: true);
+
+ Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
+
+ _parser.Run(
+ RunBehavior.UntilDone,
+ cmdHandler: null,
+ dataStream: null,
+ bulkCopyHandler: null,
+ _parser._physicalStateObj);
+ }
+
+ public override void EnlistTransaction(Transaction transaction)
+ {
+ #if NETFRAMEWORK
+ SqlConnection.VerifyExecutePermission();
+ #endif
+
+ ValidateConnectionForExecute(null);
+
+ // If a connection has a local transaction outstanding, and you try to enlist in a DTC
+ // transaction, SQL Server will roll back the local transaction and then enlist (7.0 and
+ // 2000). So, if the user tries to do this, throw.
+ if (HasLocalTransaction)
+ {
+ throw ADP.LocalTransactionPresent();
+ }
+
+ if (transaction != null && 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 & 2005, 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.
+
+ Enlist(transaction);
+ // @TODO: CER Exception Handling was removed here (see GH#3581)
+ }
+
+ internal SqlTransaction BeginSqlTransaction(
+ IsolationLevel iso,
+ string transactionName,
+ bool shouldReconnect)
+ {
+ SqlStatistics statistics = null;
+ try
+ {
+ statistics = SqlStatistics.StartTimer(Connection.Statistics);
+
+ #if NETFRAMEWORK
+ SqlConnection.ExecutePermission.Demand(); // MDAC 81476
+ #endif
+
+ ValidateConnectionForExecute(null);
+
+ if (HasLocalTransactionFromAPI)
+ {
+ throw ADP.ParallelTransactionsNotSupported(Connection);
+ }
+
+ if (iso == IsolationLevel.Unspecified)
+ {
+ // Default to ReadCommitted if unspecified.
+ iso = IsolationLevel.ReadCommitted;
+ }
+
+ SqlTransaction transaction = new(this, Connection, iso, AvailableInternalTransaction);
+ transaction.InternalTransaction.RestoreBrokenConnection = shouldReconnect;
+ ExecuteTransaction(TransactionRequest.Begin, transactionName, iso, transaction.InternalTransaction, false);
+ transaction.InternalTransaction.RestoreBrokenConnection = false;
+ return transaction;
+ }
+ // @TODO: CER Exception Handling was removed here (see GH#3581)
+ finally
+ {
+ SqlStatistics.StopTimer(statistics);
+ }
+ }
+
internal void BreakConnection()
{
SqlClientEventSource.Log.TryTraceEvent(
@@ -774,10 +1001,10 @@ internal void DecrementAsyncCount()
Interlocked.Decrement(ref _asyncCommandCount);
}
- internal override void DisconnectTransaction(SqlInternalTransaction internalTransaction) =>
+ internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) =>
_parser?.DisconnectTransaction(internalTransaction);
- // @TODO: Make internal by making the SqlInternalConnection implementation internal
+ // @TODO: Make internal by making the DbConnectionInternal implementation internal
public override void Dispose()
{
SqlClientEventSource.Log.TryAdvancedTraceEvent(
@@ -805,10 +1032,45 @@ public override void Dispose()
_fConnectionOpen = false;
}
+ _whereAbouts = null;
+
base.Dispose();
}
- internal override void ExecuteTransaction(
+ internal void EnlistNull()
+ {
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ $"SqlInternalConnection.EnlistNull | ADV | " +
+ $"Object ID {ObjectID}, " +
+ $"unenlisting.");
+
+ // 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. 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);
+
+ // Tell the base class about our enlistment
+ IsEnlistedInTransaction = false;
+ EnlistedTransaction = null;
+
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ $"SqlInternalConnection.EnlistNull | ADV | " +
+ $"Object ID {ObjectID}, " +
+ $"unenlisted.");
+
+ // The EnlistTransaction above will return an TransactionEnded event, which causes the
+ // TdsParser to clear the current transaction. In either case, when we're working with
+ // a 2005 or newer server we better not have a current transaction at this point.
+ Debug.Assert(CurrentTransaction == null, "unenlisted transaction with non-null current transaction?");
+ }
+
+ internal void ExecuteTransaction(
TransactionRequest transactionRequest,
string name,
System.Data.IsolationLevel iso,
@@ -842,6 +1104,17 @@ TransactionRequest.Rollback or
ExecuteTransaction2005(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest);
}
+ internal SqlDataReader FindLiveReader(SqlCommand command)
+ {
+ SqlDataReader reader = null;
+ SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection;
+ if (referenceCollection != null)
+ {
+ reader = referenceCollection.FindLiveReader(command);
+ }
+ return reader;
+ }
+
///
/// Called by SqlConnection.RepairConnection which is a relatively expensive way of repair
/// inner connection prior to execution of request, used from EnlistTransaction,
@@ -1017,6 +1290,31 @@ internal void OnEnvChange(SqlEnvChange rec)
}
}
+ ///
+ /// If wrapCloseInAction is defined, then the action it defines will be run with the
+ /// connection close action passed in as a parameter. The close action also supports being
+ /// run asynchronously.
+ ///
+ internal void OnError(SqlException exception, bool breakConnection, Action wrapCloseInAction = null)
+ {
+ if (breakConnection)
+ {
+ DoomThisConnection();
+ }
+
+ SqlConnection connection = Connection;
+ if (connection != null)
+ {
+ connection.OnError(exception, breakConnection, wrapCloseInAction);
+ }
+ 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;
+ }
+ }
+
// @TODO: This feature is *far* too big, and has the same issues as the above OnEnvChange
// @TODO: Consider individual callbacks for the supported features and perhaps an interface of feature callbacks. Or registering with the parser what features are handleable.
// @TODO: This class should not do low-level parsing of data from the server.
@@ -1201,8 +1499,8 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
SqlClientEventSource.Log.TryTraceEvent(
$"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " +
$"Object ID {ObjectID }, " +
- $"AddOrUpdate attempted on _dbConnectionPool.AuthenticationContexts, but it did not update the new value.",
- ObjectID);
+ $"AddOrUpdate attempted on _dbConnectionPool.AuthenticationContexts, " +
+ $"but it did not update the new value.");
}
#endif
}
@@ -1669,7 +1967,7 @@ internal override bool TryReplaceConnection(
return TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
}
- internal override void ValidateConnectionForExecute(SqlCommand command)
+ internal void ValidateConnectionForExecute(SqlCommand command)
{
TdsParser parser = _parser;
if (parser == null || parser.State is TdsParserState.Broken or TdsParserState.Closed)
@@ -1746,66 +2044,77 @@ protected override void Activate(Transaction transaction)
}
}
- // @TODO: Is this suppression still required
- [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
- protected override void ChangeDatabaseInternal(string database)
- {
- // MDAC 73598 - add brackets around database
- database = SqlConnection.FixupDatabaseTransactionName(database); // @TODO: Should go to a utility method
- Task executeTask = _parser.TdsExecuteSQLBatch(
- $@"USE {database}",
- ConnectionOptions.ConnectTimeout,
- notificationRequest: null,
- _parser._physicalStateObj,
- sync: true);
-
- Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
+ protected override void CleanupTransactionOnCompletion(Transaction transaction) =>
+ DelegatedTransaction?.TransactionEnded(transaction);
- _parser.Run(
- RunBehavior.UntilDone,
- cmdHandler: null,
- dataStream: null,
- bulkCopyHandler: null,
- _parser._physicalStateObj);
- }
+ protected override DbReferenceCollection CreateReferenceCollection() =>
+ new SqlReferenceCollection();
- // @TODO: Rename to match guidelines
- protected override byte[] GetDTCAddress()
- {
- byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this));
-
- Debug.Assert(dtcAddress != null, "null dtcAddress?");
- return dtcAddress;
- }
-
- protected override void InternalDeactivate()
+ ///
+ protected override void Deactivate()
{
- // When we're deactivated, the user must have called End on all the async commands, or
- // we don't know that we're in a state that we can recover from. We doom the connection
- // in this case, to prevent odd cases when we go to the wire.
- if (_asyncCommandCount != 0)
+ try
{
- DoomThisConnection();
- }
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ $"SqlInternalConnection.Deactivate | ADV | " +
+ $"Object ID {ObjectID} deactivating, " +
+ $"Client Connection Id {Connection?.ClientConnectionId}");
- // If we're deactivating with a delegated transaction, we should not be cleaning up the
- // parser just yet, that will cause our transaction to be rolled back and the
- // connection to be reset. We'll get called again once the delegated transaction is
- // completed, and we can do it all then.
- // TODO: I think this logic cares about pooling because the pool will handle deactivation of pool-associated trasaction roots?
- if (!(IsTransactionRoot && Pool == null))
- {
- Debug.Assert(_parser != null || IsConnectionDoomed, "Deactivating a disposed connection?");
- if (_parser != null)
+ SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection;
+ referenceCollection?.Deactivate();
+
+ // When we're deactivated, the user must have called End on all the async commands,
+ // or we don't know that we're in a state that we can recover from. We doom the
+ // connection in this case, to prevent odd cases when we go to the wire.
+ if (_asyncCommandCount != 0)
{
- _parser.Deactivate(IsConnectionDoomed);
+ DoomThisConnection();
+ }
- if (!IsConnectionDoomed)
+ // If we're deactivating with a delegated transaction, we should not be cleaning up
+ // the parser just yet, that will cause our transaction to be rolled back and the
+ // connection to be reset. We'll get called again once the delegated transaction is
+ // completed, and we can do it all then.
+ // TODO: I think this logic cares about pooling because the pool will handle deactivation of pool-associated trasaction roots?
+ if (!(IsTransactionRoot && Pool == null))
+ {
+ Debug.Assert(_parser != null || IsConnectionDoomed, "Deactivating a disposed connection?");
+ if (_parser != null)
{
- ResetConnection();
+ _parser.Deactivate(IsConnectionDoomed);
+
+ if (!IsConnectionDoomed)
+ {
+ ResetConnection();
+ }
}
}
}
+ // @TODO: CER Exception Handling was removed here (see GH#3581)
+ 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();
+
+ #if NETFRAMEWORK
+ ADP.TraceExceptionWithoutRethrow(e);
+ #endif
+ }
+ }
+
+ // @TODO: Rename to match guidelines
+ protected byte[] GetDTCAddress()
+ {
+ byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this));
+
+ Debug.Assert(dtcAddress != null, "null dtcAddress?");
+ return dtcAddress;
}
protected override bool ObtainAdditionalLocksForClose()
@@ -1824,7 +2133,7 @@ protected override bool ObtainAdditionalLocksForClose()
return obtainParserLock;
}
- protected override void PropagateTransactionCookie(byte[] cookie)
+ protected void PropagateTransactionCookie(byte[] cookie)
{
_parser.PropagateDistributedTransaction(
cookie,
@@ -1845,6 +2154,13 @@ protected override void ReleaseAdditionalLocksForClose(bool lockToken)
#region Private Methods
+ private static byte[] GetTransactionCookie(Transaction transaction, byte[] whereAbouts)
+ {
+ return transaction is not null
+ ? TransactionInterop.GetExportCookie(transaction, whereAbouts)
+ : null;
+ }
+
///
/// Common code path for making one attempt to establish a connection and log in to server.
///
@@ -2015,6 +2331,213 @@ private void CompleteLogin(bool enlistOK) // @TODO: Rename as per guidelines
_parser._physicalStateObj.SniContext = SniContext.Snix_Login;
}
+ private void Enlist(Transaction transaction)
+ {
+ // This method should not be called while the connection has a reference to an active
+ // delegated transaction. Manual enlistment via SqlConnection.EnlistTransaction should
+ // catch this case and throw an exception.
+
+ // Automatic enlistment isn't possible because Sys.Tx keeps the connection alive until
+ // the transaction is completed.
+ // @TODO: What does the above mean? Is it still valid in a post-SDS world?
+
+ // TODO: why do we assert pooling status? shouldn't we just be checking whether the connection is the root of the transaction?
+ // @TODO: potential race condition, but it's an assert
+ Debug.Assert(!(IsTransactionRoot && Pool == null), "cannot defect an active delegated transaction!");
+
+ if (transaction is null)
+ {
+ if (IsEnlistedInTransaction)
+ {
+ EnlistNull();
+ }
+ else
+ {
+ // When IsEnlistedInTransaction is false, it means we are in one of two states:
+ // 1. EnlistTransaction is null, so the connection is truly not enlisted in a
+ // transaction
+ // 2. Connection is enlisted in a SqlDelegatedTransaction.
+ //
+ // For #2, we have to consider whether the delegated transaction is active. If
+ // it is not active, we allow the enlistment in the NULL transaction. If it is
+ // active, technically this is an error.
+ //
+ // However, no exception is thrown as this was the precedent (and this case is
+ // silently ignored, no error, but no enlistment either). There are two
+ // mitigations for this:
+ // 1. SqlConnection.EnlistTransaction checks that the enlisted transaction has
+ // completed before allowing a different enlistment.
+ // 2. For debug builds, the assertion at the beginning of this method checks
+ // for an enlistment in an active delegated transaction.
+ Transaction enlistedTransaction = EnlistedTransaction;
+ if (enlistedTransaction != null && enlistedTransaction.TransactionInformation.Status != TransactionStatus.Active)
+ {
+ EnlistNull();
+ }
+ }
+ }
+ else if (!transaction.Equals(EnlistedTransaction))
+ {
+ // Only enlist if it's different...
+ EnlistNonNull(transaction);
+ }
+ }
+
+ private void EnlistNonNull(Transaction transaction)
+ {
+ Debug.Assert(transaction != null, "null transaction?");
+
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ $"SqlInternalConnection.EnlistNonNull | ADV | " +
+ $"Object ID {ObjectID}, " +
+ $"Transaction Id {transaction?.TransactionInformation?.LocalIdentifier}, " +
+ $"attempting to delegate.");
+
+ bool hasDelegatedTransaction = false;
+ SqlDelegatedTransaction delegatedTransaction = new(this, transaction);
+
+ 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.
+
+ // NOTE: Global Transactions is an Azure SQL DB only feature where the Transaction
+ // Manager (TM) is not MS-DTC. Sys.Tx added APIs to support Non MS-DTC promoter
+ // types/TM in .NET 4.6.2. Following directions from .NETFX shiproom, to avoid a
+ // "hard-dependency" (compile time) on Sys.Tx, we use reflection to invoke the new
+ // APIs. Further, the IsGlobalTransaction flag indicates that this is an Azure SQL
+ // DB Transaction that could be promoted to a Global Transaction (it's always false
+ // for on-prem SQL Server). The Promote() call in SqlDelegatedTransaction makes
+ // sure that the right Sys.Tx.dll is loaded and that Global Transactions are
+ // actually allowed for this Azure SQL DB.
+ // @TODO: Revisit these comments and see if they are still necessary/desirable.
+
+ if (IsGlobalTransaction)
+ {
+ if (SysTxForGlobalTransactions.EnlistPromotableSinglePhase == null)
+ {
+ // This could be a local Azure SQL DB transaction.
+ hasDelegatedTransaction = transaction.EnlistPromotableSinglePhase(delegatedTransaction);
+ }
+ else
+ {
+ hasDelegatedTransaction = (bool)SysTxForGlobalTransactions.EnlistPromotableSinglePhase.Invoke(
+ obj: transaction,
+ parameters: [delegatedTransaction, s_globalTransactionTMID]);
+ }
+ }
+ else
+ {
+ // This is an MS-DTC distributed transaction
+ hasDelegatedTransaction = transaction.EnlistPromotableSinglePhase(delegatedTransaction);
+ }
+
+ if (hasDelegatedTransaction)
+ {
+ DelegatedTransaction = delegatedTransaction;
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ $"SqlInternalConnection.EnlistNonNull | ADV | " +
+ $"Object ID {ObjectID}, " +
+ $"Client Connection Id {Connection?.ClientConnectionId} " +
+ $"delegated to transaction {delegatedTransaction?.ObjectID} " +
+ $"with transactionId {delegatedTransaction?.Transaction?.TransactionInformation?.LocalIdentifier}");
+ }
+ }
+ 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 (Parser?.State is not TdsParserState.OpenLoggedIn)
+ {
+ // If the parser is null or its state is not openloggedin, the connection is no
+ // longer good.
+ throw;
+ }
+
+ #if NETFRAMEWORK
+ ADP.TraceExceptionWithoutRethrow(e);
+ #endif
+
+ // 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)
+ {
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ $"SqlInternalConnection.EnlistNonNull | ADV | " +
+ $"Object ID {ObjectID}, " +
+ $"delegation not possible, enlisting.");
+
+ byte[] cookie = null;
+
+ if (IsGlobalTransaction)
+ {
+ if (SysTxForGlobalTransactions.GetPromotedToken == null)
+ {
+ throw SQL.UnsupportedSysTxForGlobalTransactions();
+ }
+
+ cookie = (byte[])SysTxForGlobalTransactions.GetPromotedToken.Invoke(transaction, null);
+ }
+ else
+ {
+ if (_whereAbouts == null)
+ {
+ byte[] dtcAddress = GetDTCAddress();
+ _whereAbouts = dtcAddress ?? throw SQL.CannotGetDTCAddress();
+ }
+
+ cookie = GetTransactionCookie(transaction, _whereAbouts);
+ }
+
+ // send cookie to server to finish enlistment
+ PropagateTransactionCookie(cookie);
+
+ IsEnlistedInTransaction = true;
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ $"SqlInternalConnection.EnlistNonNull | ADV | " +
+ $"Object ID {ObjectID}, " +
+ $"Client Connection Id {Connection?.ClientConnectionId}, " +
+ $"Enlisted in transaction with transactionId {transaction?.TransactionInformation?.LocalIdentifier}");
+ }
+
+ // Tell the base class about our enlistment
+ EnlistedTransaction = transaction;
+
+ // If we're on a 2005 or newer server, and we delegate the transaction successfully, we
+ // will have begun a transaction, which produces a transaction ID that we should
+ // execute all requests on. The TdsParser will store this information as the current
+ // transaction.
+
+ // Likewise, propagating a transaction to a 2005 or newer server will produce a
+ // transaction id that The TdsParser will store as the current transaction.
+
+ // In either case, when we're working with a 2005 or newer server we better have a
+ // current transaction by now.
+
+ Debug.Assert(CurrentTransaction != null, "delegated/enlisted transaction with null current transaction?");
+ }
+
// @TODO: Rename to ExecuteTransactionInternal ... we don't have multiple server version implementations of this
private void ExecuteTransaction2005(
TransactionRequest transactionRequest,
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
index 9784117a5e..39e4f570c7 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs
@@ -15,6 +15,7 @@
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient.Connection;
namespace Microsoft.Data.SqlClient
{
@@ -230,7 +231,7 @@ private int RowNumber
private bool _hasMoreRowToCopy = false;
private bool _isAsyncBulkCopy = false;
private bool _isBulkCopyingInProgress = false;
- private SqlInternalConnectionTds.SyncAsyncLock _parserLock = null;
+ private SqlConnectionInternal.SyncAsyncLock _parserLock = null;
private SourceColumnMetadata[] _currentRowMetadata;
@@ -1156,8 +1157,8 @@ private Task ReadFromRowSourceAsync(CancellationToken cts)
}
else
{ // This will call Read for DataRows, DataTable and IDataReader (this includes all IDataReader except DbDataReader)
- // Release lock to prevent possible deadlocks
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ // Release lock to prevent possible deadlocks
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
bool semaphoreLock = internalConnection._parserLock.CanBeReleasedFromAnyThread;
internalConnection._parserLock.Release();
@@ -1366,7 +1367,7 @@ private void CreateOrValidateConnection(string method)
private void RunParser(BulkCopySimpleResultSet bulkCopyHandler = null)
{
// In case of error while reading, we should let the connection know that we already own the _parserLock
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
internalConnection.ThreadHasParserLockForClose = true;
try
@@ -1384,7 +1385,7 @@ private void RunParser(BulkCopySimpleResultSet bulkCopyHandler = null)
private void RunParserReliably(BulkCopySimpleResultSet bulkCopyHandler = null)
{
// In case of error while reading, we should let the connection know that we already own the _parserLock
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
internalConnection.ThreadHasParserLockForClose = true;
try
{
@@ -1401,7 +1402,7 @@ private void CommitTransaction()
{
if (_internalTransaction != null)
{
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
internalConnection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we have the lock
try
{
@@ -1422,7 +1423,7 @@ private void AbortTransaction()
{
if (!_internalTransaction.IsZombied)
{
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
internalConnection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we have the lock
try
{
@@ -2069,7 +2070,7 @@ private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctok
CreateOrValidateConnection(nameof(WriteToServer));
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
Debug.Assert(_parserLock == null, "Previous parser lock not cleaned");
_parserLock = internalConnection._parserLock;
@@ -2222,7 +2223,7 @@ internal void OnConnectionClosed()
private bool FireRowsCopiedEvent(long rowsCopied)
{
// Release lock to prevent possible deadlocks
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
bool semaphoreLock = internalConnection._parserLock.CanBeReleasedFromAnyThread;
internalConnection._parserLock.Release();
@@ -2578,7 +2579,7 @@ private Task CopyBatchesAsync(BulkCopySimpleResultSet internalResults, string up
while (_hasMoreRowToCopy)
{
//pre->before every batch: Transaction, BulkCmd and metadata are done.
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
if (IsCopyOption(SqlBulkCopyOptions.UseInternalTransaction))
{ //internal transaction is started prior to each batch if the Option is set.
@@ -2965,7 +2966,7 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio
_hasMoreRowToCopy = true;
Task internalResultsTask = null;
BulkCopySimpleResultSet internalResults = new BulkCopySimpleResultSet();
- SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection();
try
{
_parser = _connection.Parser;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs
index cf90d29632..ee2ee1aabd 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs
@@ -12,6 +12,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient.Connection;
namespace Microsoft.Data.SqlClient
{
@@ -266,8 +267,7 @@ private SqlDataReader GetParameterEncryptionDataReader(
// If it is async, then TryFetchInputParameterEncryptionInfo ->
// RunExecuteReaderTds would have incremented the async count. Decrement it
// when we are about to complete async execute reader.
- SqlInternalConnectionTds internalConnectionTds =
- command._activeConnection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnectionTds = command._activeConnection.GetOpenTdsConnection();
if (internalConnectionTds is not null)
{
internalConnectionTds.DecrementAsyncCount();
@@ -345,7 +345,7 @@ private SqlDataReader GetParameterEncryptionDataReaderAsync(
// If it is async, then TryFetchInputParameterEncryptionInfo ->
// RunExecuteReaderTds would have incremented the async count. Decrement it
// when we are about to complete async execute reader.
- SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnectionTds = _activeConnection.GetOpenTdsConnection();
if (internalConnectionTds is not null)
{
internalConnectionTds.DecrementAsyncCount();
@@ -769,7 +769,7 @@ private void PrepareTransparentEncryptionFinallyBlock(
if (decrementAsyncCount)
{
// Decrement the async count
- SqlInternalConnectionTds internalConnection = _activeConnection.GetOpenTdsConnection();
+ SqlConnectionInternal internalConnection = _activeConnection.GetOpenTdsConnection();
internalConnection?.DecrementAsyncCount();
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs
index 28bd1cf2ef..5d4cba4976 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs
@@ -9,6 +9,8 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
+using Microsoft.Data.ProviderBase;
+using Microsoft.Data.SqlClient.Connection;
#if NETFRAMEWORK
using System.Security.Permissions;
@@ -904,18 +906,6 @@ private void RunExecuteNonQueryTdsSetupReconnnectContinuation(
});
}
- private void SetCachedCommandExecuteNonQueryAsyncContext(ExecuteNonQueryAsyncCallContext instance)
- {
- if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
- {
- // @TODO: Add this to SqlInternalConnection
- Interlocked.CompareExchange(
- ref sqlInternalConnection.CachedCommandExecuteNonQueryAsyncContext,
- instance,
- comparand: null);
- }
- }
-
#endregion
internal sealed class ExecuteNonQueryAsyncCallContext
@@ -939,7 +929,11 @@ public void Set(
protected override void AfterCleared(SqlCommand owner)
{
- owner?.SetCachedCommandExecuteNonQueryAsyncContext(this);
+ DbConnectionInternal internalConnection = owner?._activeConnection?.InnerConnection;
+ if (internalConnection is SqlConnectionInternal sqlInternalConnection)
+ {
+ sqlInternalConnection.CachedContexts.TrySetCommandExecuteNonQueryAsyncContext(this);
+ }
}
protected override void Clear()
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs
index 332195efb5..dda6534047 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs
@@ -10,6 +10,8 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
+using Microsoft.Data.ProviderBase;
+using Microsoft.Data.SqlClient.Connection;
#if NETFRAMEWORK
using System.Security.Permissions;
@@ -979,11 +981,9 @@ private Task InternalExecuteReaderAsync(
{
returnedTask = RegisterForConnectionCloseNotification(returnedTask);
- if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
+ if (_activeConnection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- context = Interlocked.Exchange(
- ref sqlInternalConnection.CachedCommandExecuteReaderAsyncContext,
- null);
+ context = sqlInternalConnection.CachedContexts.TakeCommandExecuteReaderAsyncContext();
}
context ??= new ExecuteReaderAsyncCallContext();
@@ -1577,7 +1577,7 @@ private SqlDataReader RunExecuteReaderTds(
if (decrementAsyncCountOnFailure)
{
- if (_activeConnection.InnerConnection is SqlInternalConnectionTds innerConnectionTds)
+ if (_activeConnection.InnerConnection is SqlConnectionInternal innerConnectionTds)
{
// It may be closed
innerConnectionTds.DecrementAsyncCount();
@@ -1785,18 +1785,6 @@ private SqlDataReader RunExecuteReaderWithRetry(
this,
() => RunExecuteReader(cmdBehavior, runBehavior, returnStream, method));
- private void SetCachedCommandExecuteReaderAsyncContext(ExecuteReaderAsyncCallContext instance)
- {
- if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
- {
- // @TODO: This should be part of the sql internal connection class.
- Interlocked.CompareExchange(
- ref sqlInternalConnection.CachedCommandExecuteReaderAsyncContext,
- instance,
- null);
- }
- }
-
#endregion
internal sealed class ExecuteReaderAsyncCallContext
@@ -1824,7 +1812,11 @@ public void Set(
protected override void AfterCleared(SqlCommand owner)
{
- owner?.SetCachedCommandExecuteReaderAsyncContext(this);
+ DbConnectionInternal internalConnection = owner?._activeConnection?.InnerConnection;
+ if (internalConnection is SqlConnectionInternal sqlInternalConnection)
+ {
+ sqlInternalConnection.CachedContexts.TrySetCommandExecuteReaderAsyncContext(this);
+ }
}
protected override void Clear()
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs
index 6a18b6b1b6..add7f90407 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs
@@ -9,6 +9,8 @@
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Data.Common;
+using Microsoft.Data.ProviderBase;
+using Microsoft.Data.SqlClient.Connection;
using Microsoft.Data.SqlClient.Server;
#if NETFRAMEWORK
@@ -486,11 +488,9 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella
// @TODO: This can be cleaned up to lines if InnerConnection is always SqlInternalConnection
ExecuteXmlReaderAsyncCallContext context = null;
- if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
+ if (_activeConnection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- context = Interlocked.Exchange(
- ref sqlInternalConnection.CachedCommandExecuteXmlReaderAsyncContext,
- null);
+ context = sqlInternalConnection.CachedContexts.TakeCommandExecuteXmlReaderAsyncContext();
}
context ??= new ExecuteXmlReaderAsyncCallContext();
@@ -546,18 +546,6 @@ private Task InternalExecuteXmlReaderWithRetryAsync(CancellationToken
sender: this,
() => InternalExecuteXmlReaderAsync(cancellationToken),
cancellationToken);
-
- private void SetCachedCommandExecuteXmlReaderContext(ExecuteXmlReaderAsyncCallContext instance)
- {
- if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
- {
- // @TODO: Move this compare exchange into the SqlInternalConnection class (or better yet, do away with this context)
- Interlocked.CompareExchange(
- ref sqlInternalConnection.CachedCommandExecuteXmlReaderAsyncContext,
- instance,
- comparand: null);
- }
- }
#endregion
@@ -582,7 +570,11 @@ public void Set(
protected override void AfterCleared(SqlCommand owner)
{
- owner?.SetCachedCommandExecuteXmlReaderContext(this);
+ DbConnectionInternal internalConnection = owner?._activeConnection?.InnerConnection;
+ if (internalConnection is SqlConnectionInternal sqlInternalConnection)
+ {
+ sqlInternalConnection.CachedContexts.TrySetCommandExecuteXmlReaderAsyncContext(this);
+ }
}
protected override void Clear()
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs
index 784692cb2e..f3a85723c6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -17,6 +17,7 @@
using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.Sql;
+using Microsoft.Data.SqlClient.Connection;
using Microsoft.Data.SqlClient.Diagnostics;
#if NETFRAMEWORK
@@ -958,10 +959,10 @@ private int DefaultCommandTimeout
}
// @TODO: Should be used in more than one place to justify its existence
- private SqlInternalConnectionTds InternalTdsConnection
+ private SqlConnectionInternal InternalTdsConnection
{
// @TODO: Should check for null? Should use Connection?
- get => (SqlInternalConnectionTds)_activeConnection.InnerConnection;
+ get => (SqlConnectionInternal)_activeConnection.InnerConnection;
}
private bool IsColumnEncryptionEnabled
@@ -1084,7 +1085,7 @@ public override void Cancel()
// Note that this model is implementable because we only allow one active command
// at any one time. This code will have to change we allow multiple outstanding
// batches.
- if (_activeConnection?.InnerConnection is not SqlInternalConnectionTds connection)
+ if (_activeConnection?.InnerConnection is not SqlConnectionInternal connection)
{
// @TODO: Really this case only applies if the connection is null.
// Fail without locking
@@ -1100,7 +1101,7 @@ public override void Cancel()
{
// Make sure the connection did not get changed getting the connection and
// taking the lock. If it has, the connection has been closed.
- if (connection != _activeConnection.InnerConnection as SqlInternalConnectionTds)
+ if (connection != _activeConnection.InnerConnection as SqlConnectionInternal)
{
return;
}
@@ -2380,9 +2381,8 @@ private void CheckNotificationStateAndAutoEnlist()
// 3) database
// Obtain identity from connection.
- // @TODO: Remove cast when possible.
- SqlInternalConnectionTds internalConnection =
- (SqlInternalConnectionTds)_activeConnection.InnerConnection;
+ SqlConnectionInternal internalConnection =
+ (SqlConnectionInternal)_activeConnection.InnerConnection;
SqlDependency.IdentityUserNamePair identityUserName = internalConnection.Identity is not null
? new SqlDependency.IdentityUserNamePair(
@@ -2951,8 +2951,7 @@ private void ValidateCommand(bool isAsync, [CallerMemberName] string method = ""
}
// Ensure that the connection is open and that the parser is in the correct state
- // @TODO: Remove cast when possible.
- SqlInternalConnectionTds tdsConnection = _activeConnection.InnerConnection as SqlInternalConnectionTds;
+ SqlConnectionInternal tdsConnection = _activeConnection.InnerConnection as SqlConnectionInternal;
// Ensure that if column encryption override was used then server supports it
// @TODO: This is kinda clunky
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 4f9d9ca14f..32ba9d7148 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -816,10 +816,8 @@ public override string Database
// just return what the connection string had.
get
{
- SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection);
string result;
-
- if (innerConnection != null)
+ if (InnerConnection is SqlConnectionInternal innerConnection)
{
result = innerConnection.CurrentDatabase;
}
@@ -828,6 +826,7 @@ public override string Database
SqlConnectionString constr = (SqlConnectionString)ConnectionOptions;
result = constr != null ? constr.InitialCatalog : DbConnectionStringDefaults.InitialCatalog;
}
+
return result;
}
}
@@ -840,7 +839,7 @@ internal string SQLDNSCachingSupportedState
{
get
{
- SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds);
+ SqlConnectionInternal innerConnection = InnerConnection as SqlConnectionInternal;
string result;
if (innerConnection != null)
@@ -864,7 +863,7 @@ internal string SQLDNSCachingSupportedStateBeforeRedirect
{
get
{
- SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds);
+ SqlConnectionInternal innerConnection = InnerConnection as SqlConnectionInternal;
string result;
if (innerConnection != null)
@@ -889,10 +888,8 @@ public override string DataSource
{
get
{
- SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection);
string result;
-
- if (innerConnection != null)
+ if (InnerConnection is SqlConnectionInternal innerConnection)
{
result = innerConnection.CurrentDataSource;
}
@@ -901,6 +898,7 @@ public override string DataSource
SqlConnectionString constr = (SqlConnectionString)ConnectionOptions;
result = constr != null ? constr.DataSource : DbConnectionStringDefaults.DataSource;
}
+
return result;
}
}
@@ -918,7 +916,7 @@ public int PacketSize
{
int result;
- if (InnerConnection is SqlInternalConnectionTds innerConnection)
+ if (InnerConnection is SqlConnectionInternal innerConnection)
{
result = innerConnection.PacketSize;
}
@@ -940,7 +938,7 @@ public Guid ClientConnectionId
{
get
{
- SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds);
+ SqlConnectionInternal innerConnection = InnerConnection as SqlConnectionInternal;
if (innerConnection != null)
{
@@ -1504,7 +1502,7 @@ private void DisposeMe(bool disposing)
// For non-pooled connections we need to make sure that if the SqlConnection was not closed,
// then we release the GCHandle on the stateObject to allow it to be GCed
// For pooled connections, we will rely on the pool reclaiming the connection
- var innerConnection = (InnerConnection as SqlInternalConnectionTds);
+ var innerConnection = (InnerConnection as SqlConnectionInternal);
if ((innerConnection != null) && (!innerConnection.ConnectionOptions.Pooling))
{
var parser = innerConnection.Parser;
@@ -1744,7 +1742,7 @@ internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout)
{
if (_connectRetryCount > 0)
{
- SqlInternalConnectionTds tdsConn = GetOpenTdsConnection();
+ SqlConnectionInternal tdsConn = GetOpenTdsConnection();
if (tdsConn._sessionRecoveryAcknowledged)
{
TdsParserStateObject stateObj = tdsConn.Parser._physicalStateObj;
@@ -1845,7 +1843,7 @@ private void RepairInnerConnection()
{
return;
}
- SqlInternalConnectionTds tdsConn = InnerConnection as SqlInternalConnectionTds;
+ SqlConnectionInternal tdsConn = InnerConnection as SqlConnectionInternal;
if (tdsConn != null)
{
tdsConn.ValidateConnectionForExecute(null);
@@ -2216,7 +2214,7 @@ private bool TryOpenInner(TaskCompletionSource retry)
}
// does not require GC.KeepAlive(this) because of ReRegisterForFinalize below.
- var tdsInnerConnection = (SqlInternalConnectionTds)InnerConnection;
+ var tdsInnerConnection = (SqlConnectionInternal)InnerConnection;
Debug.Assert(tdsInnerConnection.Parser != null, "Where's the parser?");
@@ -2328,7 +2326,7 @@ internal TdsParser Parser
{
get
{
- SqlInternalConnectionTds tdsConnection = GetOpenTdsConnection();
+ SqlConnectionInternal tdsConnection = GetOpenTdsConnection();
return tdsConnection.Parser;
}
}
@@ -2429,7 +2427,7 @@ internal void ValidateConnectionForExecute(string method, SqlCommand command)
return; // execution will wait for this task later
}
}
- SqlInternalConnectionTds innerConnection = GetOpenTdsConnection(method);
+ SqlConnectionInternal innerConnection = GetOpenTdsConnection(method);
innerConnection.ValidateConnectionForExecute(command);
}
@@ -2531,9 +2529,9 @@ private void ConnectionString_Set(DbConnectionPoolKey key)
}
}
- internal SqlInternalConnectionTds GetOpenTdsConnection()
+ internal SqlConnectionInternal GetOpenTdsConnection()
{
- SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds);
+ SqlConnectionInternal innerConnection = InnerConnection as SqlConnectionInternal;
if (innerConnection == null)
{
throw ADP.ClosedConnectionError();
@@ -2541,9 +2539,9 @@ internal SqlInternalConnectionTds GetOpenTdsConnection()
return innerConnection;
}
- internal SqlInternalConnectionTds GetOpenTdsConnection(string method)
+ internal SqlConnectionInternal GetOpenTdsConnection(string method)
{
- SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds);
+ SqlConnectionInternal innerConnection = InnerConnection as SqlConnectionInternal;
if (innerConnection == null)
{
throw ADP.OpenConnectionRequired(method, InnerConnection.State);
@@ -2697,10 +2695,17 @@ private static void ChangePassword(string connectionString, SqlConnectionString
// note: This is the only case where we directly construct the internal connection, passing in the new password.
// Normally we would simply create a regular connection and open it, but there is no other way to pass the
// new password down to the constructor. This would have an unwanted impact on the connection pool.
- SqlInternalConnectionTds con = null;
+ SqlConnectionInternal con = null;
try
{
- con = new SqlInternalConnectionTds(null, connectionOptions, credential, null, newPassword, newSecurePassword, false);
+ con = new SqlConnectionInternal(
+ identity: null,
+ connectionOptions,
+ credential,
+ providerInfo: null,
+ newPassword,
+ newSecurePassword,
+ redirectedUserInstance: false);
}
finally
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
index 6f697d37e8..5c7759d921 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
@@ -643,46 +643,58 @@ protected virtual DbConnectionInternal CreateConnection(
redirectedUserInstance = true;
string instanceName;
- if (pool == null || (pool != null && pool.Count <= 0))
- { // Non-pooled or pooled and no connections in the pool.
- SqlInternalConnectionTds sseConnection = null;
- try
+ if (pool == null || pool.Count <= 0)
+ {
+ // Non-pooled or pooled and no connections in the pool.
+
+ // NOTE: Cloning connection option opt to set 'UserInstance=True' and 'Enlist=False'
+ // This first connection is established to SqlExpress to get the instance name
+ // of the UserInstance.
+ SqlConnectionString sseopt = new SqlConnectionString(
+ opt,
+ opt.DataSource,
+ userInstance: true,
+ setEnlistValue: false);
+
+ SqlConnectionInternal sseConnection = new SqlConnectionInternal(
+ identity,
+ sseopt,
+ key.Credential,
+ providerInfo: null,
+ newPassword: string.Empty,
+ newSecurePassword: null,
+ redirectedUserInstance: false,
+ applyTransientFaultHandling: applyTransientFaultHandling,
+ sspiContextProvider: key.SspiContextProvider);
+ using (sseConnection)
{
- // We throw an exception in case of a failure
- // NOTE: Cloning connection option opt to set 'UserInstance=True' and 'Enlist=False'
- // This first connection is established to SqlExpress to get the instance name
- // of the UserInstance.
- SqlConnectionString sseopt = new SqlConnectionString(opt, opt.DataSource, userInstance: true, setEnlistValue: false);
- sseConnection = new SqlInternalConnectionTds(identity, sseopt, key.Credential, null, "", null, false, applyTransientFaultHandling: applyTransientFaultHandling, sspiContextProvider: key.SspiContextProvider);
- // NOTE: Retrieve here. This user instance name will be used below to connect to the Sql Express User Instance.
+ // NOTE: Retrieve here. This user instance name will be
+ // used below to connect to the SQL Express User Instance.
instanceName = sseConnection.InstanceName;
// Set future transient fault handling based on connection options
sqlOwningConnection._applyTransientFaultHandling = opt != null && opt.ConnectRetryCount > 0;
- if (!instanceName.StartsWith("\\\\.\\", StringComparison.Ordinal))
+ if (!instanceName.StartsWith(@"\\.\", StringComparison.Ordinal))
{
throw SQL.NonLocalSSEInstance();
}
if (pool != null)
- { // Pooled connection - cache result
+ {
+ // Pooled connection - cache result
SqlConnectionPoolProviderInfo providerInfo = (SqlConnectionPoolProviderInfo)pool.ProviderInfo;
+
// No lock since we are already in creation mutex
providerInfo.InstanceName = instanceName;
}
}
- finally
- {
- if (sseConnection != null)
- {
- sseConnection.Dispose();
- }
- }
}
else
- { // Cached info from pool.
+ {
+ // Cached info from pool.
SqlConnectionPoolProviderInfo providerInfo = (SqlConnectionPoolProviderInfo)pool.ProviderInfo;
+
// No lock since we are already in creation mutex
instanceName = providerInfo.InstanceName;
}
@@ -694,7 +706,7 @@ protected virtual DbConnectionInternal CreateConnection(
poolGroupProviderInfo = null; // null so we do not pass to constructor below...
}
- return new SqlInternalConnectionTds(
+ return new SqlConnectionInternal(
identity,
opt,
key.Credential,
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index 22da8a07a3..adb06498c1 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -24,6 +24,7 @@
using System.Xml;
using Microsoft.Data.Common;
using Microsoft.Data.ProviderBase;
+using Microsoft.Data.SqlClient.Connection;
using Microsoft.Data.SqlClient.DataClassification;
using Microsoft.Data.SqlClient.Server;
using Microsoft.Data.SqlTypes;
@@ -4101,10 +4102,11 @@ internal TdsOperationStatus TryReadColumnInternal(int i, bool readHeaderOnly = f
{
// reset snapshot to save memory use. We can safely do that here because all SqlDataReader values are stable.
// The retry logic can use the current values to get back to the right state.
- if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
+ if (_connection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- sqlInternalConnection.CachedDataReaderSnapshot = _snapshot;
+ sqlInternalConnection.CachedContexts.TrySetDataReaderSnapshot(_snapshot);
}
+
_snapshot = null;
PrepareAsyncInvocation(useSnapshot: true);
}
@@ -5038,9 +5040,9 @@ public override Task ReadAsync(CancellationToken cancellationToken)
}
ReadAsyncCallContext context = null;
- if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
+ if (_connection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- context = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderReadAsyncContext, null);
+ context = sqlInternalConnection.CachedContexts.TakeDataReaderReadAsyncContext();
}
if (context is null)
{
@@ -5085,10 +5087,11 @@ private static Task ReadAsyncExecute(Task task, object state)
if (!hasReadRowToken)
{
hasReadRowToken = true;
- if (reader.Connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
+ if (reader.Connection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- sqlInternalConnection.CachedDataReaderSnapshot = reader._snapshot;
+ sqlInternalConnection.CachedContexts.TrySetDataReaderSnapshot(reader._snapshot);
}
+
reader._snapshot = null;
reader.PrepareAsyncInvocation(useSnapshot: true);
}
@@ -5106,14 +5109,6 @@ private static Task ReadAsyncExecute(Task task, object state)
return reader.ExecuteAsyncCall(context);
}
- private void SetCachedReadAsyncCallContext(ReadAsyncCallContext instance)
- {
- if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
- {
- Interlocked.CompareExchange(ref sqlInternalConnection.CachedDataReaderReadAsyncContext, instance, null);
- }
- }
-
///
override public Task IsDBNullAsync(int i, CancellationToken cancellationToken)
{
@@ -5214,9 +5209,9 @@ override public Task IsDBNullAsync(int i, CancellationToken cancellationTo
}
IsDBNullAsyncCallContext context = null;
- if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
+ if (_connection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- context = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderIsDBNullContext, null);
+ context = sqlInternalConnection.CachedContexts.TakeDataReaderIsDbNullContext();
}
if (context is null)
{
@@ -5257,14 +5252,6 @@ private static Task IsDBNullAsyncExecute(Task task, object state)
}
}
- private void SetCachedIDBNullAsyncCallContext(IsDBNullAsyncCallContext instance)
- {
- if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
- {
- Interlocked.CompareExchange(ref sqlInternalConnection.CachedDataReaderIsDBNullContext, instance, null);
- }
- }
-
///
override public Task GetFieldValueAsync(int i, CancellationToken cancellationToken)
{
@@ -5503,7 +5490,11 @@ internal ReadAsyncCallContext()
protected override void AfterCleared(SqlDataReader owner)
{
- owner.SetCachedReadAsyncCallContext(this);
+ DbConnectionInternal internalConnection = owner?._connection?.InnerConnection;
+ if (internalConnection is SqlConnectionInternal sqlInternalConnection)
+ {
+ sqlInternalConnection.CachedContexts.TrySetDataReaderReadAsyncContext(this);
+ }
}
}
@@ -5519,7 +5510,11 @@ internal IsDBNullAsyncCallContext() { }
protected override void AfterCleared(SqlDataReader owner)
{
- owner.SetCachedIDBNullAsyncCallContext(this);
+ DbConnectionInternal internalConnection = owner?._connection?.InnerConnection;
+ if (internalConnection is SqlConnectionInternal sqlInternalConnection)
+ {
+ sqlInternalConnection.CachedContexts.TrySetDataReaderIsDbNullContext(this);
+ }
}
}
@@ -5800,9 +5795,9 @@ private void PrepareAsyncInvocation(bool useSnapshot)
if (_snapshot == null)
{
- if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
+ if (_connection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- _snapshot = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderSnapshot, null) ?? new Snapshot();
+ _snapshot = sqlInternalConnection.CachedContexts.TakeDataReaderSnapshot() ?? new Snapshot();
}
else
{
@@ -5880,10 +5875,11 @@ private void CleanupAfterAsyncInvocationInternal(TdsParserStateObject stateObj,
stateObj._permitReplayStackTraceToDiffer = false;
#endif
- if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
+ if (_connection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- sqlInternalConnection.CachedDataReaderSnapshot = _snapshot;
+ sqlInternalConnection.CachedContexts.TrySetDataReaderSnapshot(_snapshot);
}
+
// We are setting this to null inside the if-statement because stateObj==null means that the reader hasn't been initialized or has been closed (either way _snapshot should already be null)
_snapshot = null;
}
@@ -5922,10 +5918,11 @@ private void SwitchToAsyncWithoutSnapshot()
Debug.Assert(_snapshot != null, "Should currently have a snapshot");
Debug.Assert(_stateObj != null && !_stateObj._asyncReadWithoutSnapshot, "Already in async without snapshot");
- if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
+ if (_connection?.InnerConnection is SqlConnectionInternal sqlInternalConnection)
{
- sqlInternalConnection.CachedDataReaderSnapshot = _snapshot;
+ sqlInternalConnection.CachedContexts.TrySetDataReaderSnapshot(_snapshot);
}
+
_snapshot = null;
_stateObj.ResetSnapshot();
_stateObj._asyncReadWithoutSnapshot = true;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs
index eb7b65a497..c463b2aeb4 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs
@@ -8,6 +8,7 @@
using System.Threading;
using System.Transactions;
using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient.Connection;
namespace Microsoft.Data.SqlClient
{
@@ -24,7 +25,7 @@ internal sealed class SqlDelegatedTransaction : IPromotableSinglePhaseNotificati
// or notifications of same. Updates to the connection's association with the transaction or to the connection pool
// may be initiated here AFTER the connection lock is released, but should NOT fall under this class's locking strategy.
- private SqlInternalConnection _connection; // the internal connection that is the root of the transaction
+ private SqlConnectionInternal _connection; // the internal connection that is the root of the transaction
private System.Data.IsolationLevel _isolationLevel; // the IsolationLevel of the transaction we delegated to the server
private SqlInternalTransaction _internalTransaction; // the SQL Server transaction we're delegating to
@@ -32,7 +33,7 @@ internal sealed class SqlDelegatedTransaction : IPromotableSinglePhaseNotificati
private bool _active; // Is the transaction active?
- internal SqlDelegatedTransaction(SqlInternalConnection connection, Transaction tx)
+ internal SqlDelegatedTransaction(SqlConnectionInternal connection, Transaction tx)
{
Debug.Assert(connection != null, "null connection?");
_connection = connection;
@@ -78,7 +79,7 @@ public void Initialize()
{
// if we get here, then we know for certain that we're the delegated
// transaction.
- SqlInternalConnection connection = _connection;
+ SqlConnectionInternal connection = _connection;
SqlConnection usersConnection = connection.Connection;
SqlClientEventSource.Log.TryTraceEvent("SqlDelegatedTransaction.Initialize | RES | CPOOL | Object Id {0}, Client Connection Id {1}, delegating transaction.", ObjectID, usersConnection?.ClientConnectionId);
@@ -119,7 +120,7 @@ public byte[] Promote()
// Don't read values off of the connection outside the lock unless it doesn't really matter
// from an operational standpoint (i.e. logging connection's ObjectID should be fine,
// but the PromotedDTCToken can change over calls. so that must be protected).
- SqlInternalConnection connection = GetValidConnection();
+ SqlConnectionInternal connection = GetValidConnection();
Exception promoteException;
byte[] returnValue = null;
@@ -212,7 +213,7 @@ public byte[] Promote()
public void Rollback(SinglePhaseEnlistment enlistment)
{
Debug.Assert(enlistment != null, "null enlistment?");
- SqlInternalConnection connection = GetValidConnection();
+ SqlConnectionInternal connection = GetValidConnection();
if (connection != null)
{
@@ -280,7 +281,7 @@ public void Rollback(SinglePhaseEnlistment enlistment)
public void SinglePhaseCommit(SinglePhaseEnlistment enlistment)
{
Debug.Assert(enlistment != null, "null enlistment?");
- SqlInternalConnection connection = GetValidConnection();
+ SqlConnectionInternal connection = GetValidConnection();
if (connection != null)
{
@@ -385,7 +386,7 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment)
// the transaction).
internal void TransactionEnded(Transaction transaction)
{
- SqlInternalConnection connection = _connection;
+ SqlConnectionInternal connection = _connection;
if (connection != null)
{
@@ -406,9 +407,9 @@ internal void TransactionEnded(Transaction transaction)
}
// Check for connection validity
- private SqlInternalConnection GetValidConnection()
+ private SqlConnectionInternal GetValidConnection()
{
- SqlInternalConnection connection = _connection;
+ SqlConnectionInternal connection = _connection;
if (connection == null && Transaction.TransactionInformation.Status != TransactionStatus.Aborted)
{
throw ADP.ObjectDisposed(this);
@@ -420,7 +421,7 @@ private SqlInternalConnection GetValidConnection()
// Dooms connection and throws and error if not a valid, active, delegated transaction for the given
// connection. Designed to be called AFTER a lock is placed on the connection, otherwise a normal return
// may not be trusted.
- private void ValidateActiveOnConnection(SqlInternalConnection connection)
+ private void ValidateActiveOnConnection(SqlConnectionInternal connection)
{
bool valid = _active && (connection == _connection) && (connection.DelegatedTransaction == this);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs
index e070bb7d2e..b392a94521 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs
@@ -10,6 +10,7 @@
using System.Globalization;
using System.Runtime.Serialization;
using System.Text;
+using Microsoft.Data.SqlClient.Connection;
namespace Microsoft.Data.SqlClient
{
@@ -174,7 +175,7 @@ public override string ToString()
internal static SqlException CreateException(
SqlError error,
string serverVersion,
- SqlInternalConnectionTds internalConnection,
+ SqlConnectionInternal internalConnection,
Exception innerException = null)
{
SqlErrorCollection errorCollection = new() { error };
@@ -200,7 +201,7 @@ internal static SqlException CreateException(
internal static SqlException CreateException(
SqlErrorCollection errorCollection,
string serverVersion,
- SqlInternalConnectionTds internalConnection,
+ SqlConnectionInternal internalConnection,
Exception innerException = null)
{
return CreateException(
@@ -214,7 +215,7 @@ internal static SqlException CreateException(
internal static SqlException CreateException(
SqlErrorCollection errorCollection,
string serverVersion,
- SqlInternalConnectionTds internalConnection,
+ SqlConnectionInternal internalConnection,
Exception innerException = null,
SqlBatchCommand batchCommand = null)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs
deleted file mode 100644
index 46852a5df4..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs
+++ /dev/null
@@ -1,611 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Data.Common;
-using System.Diagnostics;
-using System.Transactions;
-using Microsoft.Data.Common;
-using Microsoft.Data.ProviderBase;
-
-#if NETFRAMEWORK
-using System.Runtime.CompilerServices;
-using System.Runtime.ConstrainedExecution;
-#endif
-
-namespace Microsoft.Data.SqlClient
-{
- internal abstract class SqlInternalConnection : DbConnectionInternal
- {
- ///
- /// Cache the whereabouts (DTC Address) for exporting.
- ///
- private byte[] _whereAbouts;
-
- ///
- /// ID of the Azure SQL DB Transaction Manager (Non-MSDTC)
- ///
- private static readonly Guid s_globalTransactionTMID = new("1c742caf-6680-40ea-9c26-6b6846079764");
-
- internal SqlCommand.ExecuteReaderAsyncCallContext CachedCommandExecuteReaderAsyncContext;
- internal SqlCommand.ExecuteNonQueryAsyncCallContext CachedCommandExecuteNonQueryAsyncContext;
- internal SqlCommand.ExecuteXmlReaderAsyncCallContext CachedCommandExecuteXmlReaderAsyncContext;
- internal SqlDataReader.Snapshot CachedDataReaderSnapshot;
- internal SqlDataReader.IsDBNullAsyncCallContext CachedDataReaderIsDBNullContext;
- internal SqlDataReader.ReadAsyncCallContext CachedDataReaderReadAsyncContext;
-
- ///
- /// Constructs a new SqlInternalConnection object using the provided connection options.
- ///
- /// The options to use for this connection.
- internal SqlInternalConnection(SqlConnectionString connectionOptions) : base()
- {
- Debug.Assert(connectionOptions != null, "null connectionOptions?");
- ConnectionOptions = connectionOptions;
- }
-
- #region Properties
-
- // 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.
- ///
- /// TODO: need to understand this property better
- ///
- virtual internal SqlInternalTransaction AvailableInternalTransaction => CurrentTransaction;
-
- ///
- /// A reference to the SqlConnection that owns this internal connection.
- ///
- internal SqlConnection Connection => (SqlConnection)Owner;
-
- ///
- /// The connection options to be used for this connection.
- ///
- internal SqlConnectionString ConnectionOptions { get; init; }
-
- ///
- /// The current database for this connection.
- /// Null if the connection is not open yet.
- ///
- internal string CurrentDatabase { get; set; }
-
- ///
- /// The current data source for this connection.
- ///
- /// if connection is not open yet, CurrentDataSource is null
- /// if connection is open:
- /// * for regular connections, it is set to the Data Source value from connection string
- /// * for failover connections, it is set to the FailoverPartner value from the connection string
- ///
- internal string CurrentDataSource { get; set; }
-
- ///
- /// The Transaction currently associated with this connection.
- ///
- abstract internal SqlInternalTransaction CurrentTransaction { get; }
-
- ///
- /// The delegated (or promoted) transaction this connection is responsible for.
- ///
- internal SqlDelegatedTransaction DelegatedTransaction { get; set; }
-
- ///
- /// Whether this connection has a local (non-delegated) transaction.
- ///
- internal bool HasLocalTransaction
- {
- get
- {
- SqlInternalTransaction currentTransaction = CurrentTransaction;
- bool result = currentTransaction != null && currentTransaction.IsLocal;
- return result;
- }
- }
-
- ///
- /// Whether this connection has a local transaction started from the API (i.e., SqlConnection.BeginTransaction)
- /// or had a TSQL transaction and later got wrapped by an API transaction.
- ///
- internal bool HasLocalTransactionFromAPI
- {
- get
- {
- SqlInternalTransaction currentTransaction = CurrentTransaction;
- bool result = currentTransaction != null && currentTransaction.HasParentTransaction;
- return result;
- }
- }
-
- ///
- /// Whether the server version is SQL Server 2008 or newer.
- ///
- abstract internal bool Is2008OrNewer { get; }
-
- ///
- /// Whether this connection is to an Azure SQL Database.
- ///
- internal bool IsAzureSqlConnection { get; set; }
-
- ///
- /// Indicates whether the connection is currently enlisted in a transaction.
- ///
- internal bool IsEnlistedInTransaction { get; private set; }
-
- ///
- /// Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction)
- /// TODO: overlaps with IsGlobalTransactionsEnabledForServer, need to consolidate to avoid bugs
- ///
- internal bool IsGlobalTransaction { get; set; }
-
- ///
- /// Whether Global Transactions are enabled. Only supported by Azure SQL.
- /// False if disabled or connected to on-prem SQL Server.
- ///
- internal bool IsGlobalTransactionsEnabledForServer { get; set; }
-
- ///
- /// Whether this connection is locked for bulk copy operations.
- ///
- abstract internal bool IsLockedForBulkCopy { get; }
-
- ///
- /// Whether this connection is the root of a delegated or promoted transaction.
- ///
- override internal bool IsTransactionRoot
- {
- get
- {
- SqlDelegatedTransaction delegatedTransaction = DelegatedTransaction;
- return delegatedTransaction != null && (delegatedTransaction.IsActive);
- }
- }
-
- ///
- /// TODO: need to understand this property better
- ///
- abstract internal SqlInternalTransaction PendingTransaction { get; }
-
- ///
- /// A token returned by the server when we promote transaction.
- ///
- internal byte[] PromotedDtcToken { get; set; }
-
- #endregion
-
- override public DbTransaction BeginTransaction(System.Data.IsolationLevel iso)
- {
- return BeginSqlTransaction(iso, null, false);
- }
-
- virtual internal SqlTransaction BeginSqlTransaction(System.Data.IsolationLevel iso, string transactionName, bool shouldReconnect)
- {
- SqlStatistics statistics = null;
- try
- {
- statistics = SqlStatistics.StartTimer(Connection.Statistics);
-
- #if NETFRAMEWORK
- SqlConnection.ExecutePermission.Demand(); // MDAC 81476
- #endif
-
- ValidateConnectionForExecute(null);
-
- if (HasLocalTransactionFromAPI)
- {
- throw ADP.ParallelTransactionsNotSupported(Connection);
- }
-
- if (iso == System.Data.IsolationLevel.Unspecified)
- {
- iso = System.Data.IsolationLevel.ReadCommitted; // Default to ReadCommitted if unspecified.
- }
-
- SqlTransaction transaction = new(this, Connection, iso, AvailableInternalTransaction);
- transaction.InternalTransaction.RestoreBrokenConnection = shouldReconnect;
- ExecuteTransaction(TransactionRequest.Begin, transactionName, iso, transaction.InternalTransaction, false);
- transaction.InternalTransaction.RestoreBrokenConnection = false;
- return transaction;
- }
- // @TODO: CER Exception Handling was removed here (see GH#3581)
- finally
- {
- SqlStatistics.StopTimer(statistics);
- }
- }
-
- override public void ChangeDatabase(string database)
- {
- if (string.IsNullOrEmpty(database))
- {
- throw ADP.EmptyDatabaseName();
- }
-
- ValidateConnectionForExecute(null);
-
- ChangeDatabaseInternal(database); // do the real work...
- }
-
- abstract protected void ChangeDatabaseInternal(string database);
-
- override protected void CleanupTransactionOnCompletion(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 (delegatedTransaction != null)
- {
- delegatedTransaction.TransactionEnded(transaction);
- }
- }
-
- override protected DbReferenceCollection CreateReferenceCollection()
- {
- return new SqlReferenceCollection();
- }
-
- ///
- override protected void Deactivate()
- {
- try
- {
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlInternalConnection.Deactivate | ADV | Object Id {0} deactivating, Client Connection Id {1}", ObjectID, Connection?.ClientConnectionId);
-
- SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection;
- if (referenceCollection != null)
- {
- referenceCollection.Deactivate();
- }
-
- // Invoke subclass-specific deactivation logic
- InternalDeactivate();
- }
- // @TODO: CER Exception Handling was removed here (see GH#3581)
- 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();
-#if NETFRAMEWORK
- ADP.TraceExceptionWithoutRethrow(e);
-#endif
- }
- }
-
- abstract internal void DisconnectTransaction(SqlInternalTransaction internalTransaction);
-
- override public void Dispose()
- {
- _whereAbouts = null;
- base.Dispose();
- }
-
- protected void Enlist(Transaction tx)
- {
- // This method should not be called while the connection has a
- // reference to an active delegated transaction.
- // Manual enlistment via SqlConnection.EnlistTransaction
- // should catch this case and throw an exception.
- //
- // Automatic enlistment isn't possible because
- // Sys.Tx keeps the connection alive until the transaction is completed.
- // TODO: why do we assert pooling status? shouldn't we just be checking
- // whether the connection is the root of the transaction?
- Debug.Assert(!(IsTransactionRoot && Pool == null), "cannot defect an active delegated transaction!"); // potential race condition, but it's an assert
-
- if (tx == null)
- {
- if (IsEnlistedInTransaction)
- {
- EnlistNull();
- }
- else
- {
- // When IsEnlistedInTransaction is false, it means we are in one of two states:
- // 1. EnlistTransaction is null, so the connection is truly not enlisted in a transaction, or
- // 2. Connection is enlisted in a SqlDelegatedTransaction.
- //
- // For #2, we have to consider whether or not the delegated transaction is active.
- // If it is not active, we allow the enlistment in the NULL transaction.
- //
- // If it is active, technically this is an error.
- // However, no exception is thrown as this was the precedent (and this case is silently ignored, no error, but no enlistment either).
- // There are two mitigations for this:
- // 1. SqlConnection.EnlistTransaction checks that the enlisted transaction has completed before allowing a different enlistment.
- // 2. For debug builds, the assert at the beginning of this method checks for an enlistment in an active delegated transaction.
- Transaction enlistedTransaction = EnlistedTransaction;
- if (enlistedTransaction != null && enlistedTransaction.TransactionInformation.Status != TransactionStatus.Active)
- {
- EnlistNull();
- }
- }
- }
- // Only enlist if it's different...
- else if (!tx.Equals(EnlistedTransaction))
- { // WebData 20000024 - Must use Equals, not !=
- EnlistNonNull(tx);
- }
- }
-
- private void EnlistNonNull(Transaction tx)
- {
- Debug.Assert(tx != null, "null transaction?");
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlInternalConnection.EnlistNonNull | ADV | Object {0}, Transaction Id {1}, attempting to delegate.", ObjectID, tx?.TransactionInformation?.LocalIdentifier);
- bool hasDelegatedTransaction = false;
-
- // Promotable transactions are only supported on 2005
- // servers or newer.
- SqlDelegatedTransaction delegatedTransaction = new(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.
-
- // NOTE: Global Transactions is an Azure SQL DB only
- // feature where the Transaction Manager (TM) is not
- // MS-DTC. Sys.Tx added APIs to support Non MS-DTC
- // promoter types/TM in .NET 4.6.2. Following directions
- // from .NETFX shiproom, to avoid a "hard-dependency"
- // (compile time) on Sys.Tx, we use reflection to invoke
- // the new APIs. Further, the IsGlobalTransaction flag
- // indicates that this is an Azure SQL DB Transaction
- // that could be promoted to a Global Transaction (it's
- // always false for on-prem Sql Server). The Promote()
- // call in SqlDelegatedTransaction makes sure that the
- // right Sys.Tx.dll is loaded and that Global Transactions
- // are actually allowed for this Azure SQL DB.
-
- if (IsGlobalTransaction)
- {
- if (SysTxForGlobalTransactions.EnlistPromotableSinglePhase == null)
- {
- // This could be a local Azure SQL DB transaction.
- hasDelegatedTransaction = tx.EnlistPromotableSinglePhase(delegatedTransaction);
- }
- else
- {
- hasDelegatedTransaction = (bool)SysTxForGlobalTransactions.EnlistPromotableSinglePhase.Invoke(tx, new object[] { delegatedTransaction, s_globalTransactionTMID });
- }
- }
- else
- {
- // This is an MS-DTC distributed transaction
- hasDelegatedTransaction = tx.EnlistPromotableSinglePhase(delegatedTransaction);
- }
-
- if (hasDelegatedTransaction)
- {
- DelegatedTransaction = delegatedTransaction;
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlInternalConnection.EnlistNonNull | ADV | Object Id {0}, Client Connection Id {1} delegated to transaction {1} with transactionId {2}", ObjectID, Connection?.ClientConnectionId, delegatedTransaction?.ObjectID, delegatedTransaction?.Transaction?.TransactionInformation?.LocalIdentifier);
- }
- }
- 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 null or its state is not openloggedin, the connection is no longer good.
- if (this is SqlInternalConnectionTds tdsConnection)
- {
- TdsParser parser = tdsConnection.Parser;
- if (parser == null || parser.State != TdsParserState.OpenLoggedIn)
- {
- throw;
- }
- }
-
-#if NETFRAMEWORK
- ADP.TraceExceptionWithoutRethrow(e);
-#endif
- // 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)
- {
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlInternalConnection.EnlistNonNull | ADV | Object Id {0}, delegation not possible, enlisting.", ObjectID);
- byte[] cookie = null;
-
- if (IsGlobalTransaction)
- {
- if (SysTxForGlobalTransactions.GetPromotedToken == null)
- {
- throw SQL.UnsupportedSysTxForGlobalTransactions();
- }
-
- cookie = (byte[])SysTxForGlobalTransactions.GetPromotedToken.Invoke(tx, null);
- }
- else
- {
- if (_whereAbouts == null)
- {
- byte[] dtcAddress = GetDTCAddress();
- _whereAbouts = dtcAddress ?? throw SQL.CannotGetDTCAddress();
- }
- cookie = GetTransactionCookie(tx, _whereAbouts);
- }
-
- // send cookie to server to finish enlistment
- PropagateTransactionCookie(cookie);
-
- IsEnlistedInTransaction = true;
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlInternalConnection.EnlistNonNull | ADV | Object Id {0}, Client Connection Id {1}, Enlisted in transaction with transactionId {2}", ObjectID, Connection?.ClientConnectionId, tx?.TransactionInformation?.LocalIdentifier);
- }
-
- EnlistedTransaction = tx; // Tell the base class about our enlistment
-
-
- // If we're on a 2005 or newer server, and 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 2005 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 2005 or newer server
- // we better have a current transaction by now.
-
- Debug.Assert(CurrentTransaction != null, "delegated/enlisted transaction with null current transaction?");
- }
-
- internal void EnlistNull()
- {
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlInternalConnection.EnlistNull | ADV | Object Id {0}, unenlisting.", 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
-
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlInternalConnection.EnlistNull | ADV | Object Id {0}, unenlisted.", 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 2005 or newer server
- // we better not have a current transaction at this point.
-
- Debug.Assert(CurrentTransaction == null, "unenlisted transaction with non-null current transaction?"); // verify it!
- }
-
- override public void EnlistTransaction(Transaction transaction)
- {
-#if NETFRAMEWORK
- SqlConnection.VerifyExecutePermission();
-#endif
- 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 (transaction != null && 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 & 2005, 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.
-
- Enlist(transaction);
- // @TODO: CER Exception Handling was removed here (see GH#3581)
- }
-
- abstract internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest);
-
- internal SqlDataReader FindLiveReader(SqlCommand command)
- {
- SqlDataReader reader = null;
- SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection;
- if (referenceCollection != null)
- {
- reader = referenceCollection.FindLiveReader(command);
- }
- return reader;
- }
-
- abstract protected byte[] GetDTCAddress();
-
- static private byte[] GetTransactionCookie(Transaction transaction, byte[] whereAbouts)
- {
- byte[] transactionCookie = null;
- if (transaction != null)
- {
- transactionCookie = TransactionInterop.GetExportCookie(transaction, whereAbouts);
- }
- return transactionCookie;
- }
-
- virtual protected void InternalDeactivate()
- {
- }
-
- // If wrapCloseInAction is defined, then the action it defines will be run with the connection close action passed in as a parameter
- // The close action also supports being run asynchronously
- internal void OnError(SqlException exception, bool breakConnection, Action wrapCloseInAction = null)
- {
- if (breakConnection)
- {
- DoomThisConnection();
- }
-
- SqlConnection connection = Connection;
- if (connection != null)
- {
- connection.OnError(exception, breakConnection, wrapCloseInAction);
- }
- 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 ValidateConnectionForExecute(SqlCommand command);
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs
index 5cb7b990dc..cc43c59e99 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs
@@ -7,6 +7,7 @@
using System.Diagnostics;
using System.Threading;
using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient.Connection;
namespace Microsoft.Data.SqlClient
{
@@ -36,7 +37,7 @@ sealed internal class SqlInternalTransaction
private readonly TransactionType _transactionType;
private long _transactionId; // passed in the MARS headers
private int _openResultCount; // passed in the MARS headers
- private SqlInternalConnection _innerConnection;
+ private SqlConnectionInternal _innerConnection;
private bool _disposing; // used to prevent us from throwing exceptions while we're disposing
private WeakReference _parent; // weak ref to the outer transaction object; needs to be weak to allow GC to occur.
@@ -46,11 +47,19 @@ sealed internal class SqlInternalTransaction
internal bool RestoreBrokenConnection { get; set; }
internal bool ConnectionHasBeenRestored { get; set; }
- internal SqlInternalTransaction(SqlInternalConnection innerConnection, TransactionType type, SqlTransaction outerTransaction) : this(innerConnection, type, outerTransaction, NullTransactionId)
+ internal SqlInternalTransaction(
+ SqlConnectionInternal innerConnection,
+ TransactionType type,
+ SqlTransaction outerTransaction)
+ : this(innerConnection, type, outerTransaction, NullTransactionId)
{
}
- internal SqlInternalTransaction(SqlInternalConnection innerConnection, TransactionType type, SqlTransaction outerTransaction, long transactionId)
+ internal SqlInternalTransaction(
+ SqlConnectionInternal innerConnection,
+ TransactionType type,
+ SqlTransaction outerTransaction,
+ long transactionId)
{
SqlClientEventSource.Log.TryPoolerTraceEvent("SqlInternalTransaction.ctor | RES | CPOOL | Object Id {0}, Created for connection {1}, outer transaction {2}, Type {3}", ObjectID, innerConnection.ObjectID, outerTransaction?.ObjectId, (int)type);
_innerConnection = innerConnection;
@@ -187,7 +196,7 @@ private void CheckTransactionLevelAndZombie()
internal void CloseFromConnection()
{
- SqlInternalConnection innerConnection = _innerConnection;
+ SqlConnectionInternal innerConnection = _innerConnection;
Debug.Assert(innerConnection != null, "How can we be here if the connection is null?");
SqlClientEventSource.Log.TryPoolerTraceEvent("SqlInternalTransaction.CloseFromConnection | RES | CPOOL | Object Id {0}, Closing transaction", ObjectID);
@@ -452,7 +461,7 @@ internal void Zombie()
ZombieParent();
- SqlInternalConnection innerConnection = _innerConnection;
+ SqlConnectionInternal innerConnection = _innerConnection;
_innerConnection = null;
if (innerConnection != null)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs
index 011a5ab364..28f8a3d6db 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs
@@ -9,6 +9,7 @@
using System.Diagnostics;
using System.Threading;
using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient.Connection;
using Microsoft.Data.SqlClient.Diagnostics;
#if NETFRAMEWORK
using System.Runtime.CompilerServices;
@@ -28,7 +29,7 @@ public sealed class SqlTransaction : DbTransaction
private bool _isFromApi;
internal SqlTransaction(
- SqlInternalConnection internalConnection,
+ SqlConnectionInternal internalConnection,
SqlConnection con,
IsolationLevel iso,
SqlInternalTransaction internalTransaction)
@@ -41,11 +42,15 @@ internal SqlTransaction(
if (internalTransaction == null)
{
- InternalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this);
+ InternalTransaction = new SqlInternalTransaction(
+ internalConnection,
+ TransactionType.LocalFromAPI,
+ this);
}
else
{
- Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!");
+ Debug.Assert(internalConnection.CurrentTransaction == internalTransaction,
+ "Unexpected Parser.CurrentTransaction state!");
InternalTransaction = internalTransaction;
InternalTransaction.InitParent(this);
}
@@ -289,7 +294,7 @@ internal void Zombie()
// For Yukon, we have to defer "zombification" until we get past the users' next
// rollback, else we'll throw an exception there that is a breaking change. Of course,
// if the connection is already closed, then we're free to zombify...
- if (_connection.InnerConnection is SqlInternalConnection internalConnection && !_isFromApi)
+ if (_connection.InnerConnection is SqlConnectionInternal && !_isFromApi)
{
SqlClientEventSource.Log.TryAdvancedTraceEvent(
"SqlTransaction.Zombie | ADV | Object Id {0} yukon deferred zombie",
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
index 540cf3b95c..7c7d3e8c87 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -16,6 +16,7 @@
using System.Transactions;
using Interop.Common.Sni;
using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient.Connection;
#if NETFRAMEWORK
using System.Runtime.InteropServices;
@@ -867,7 +868,9 @@ internal static Exception SqlDependencyNoMatchingServerDatabaseStart()
//
// SQL.SqlDelegatedTransaction
//
- static internal Exception CannotCompleteDelegatedTransactionWithOpenResults(SqlInternalConnectionTds internalConnection, bool marsOn)
+ static internal Exception CannotCompleteDelegatedTransactionWithOpenResults(
+ SqlConnectionInternal internalConnection,
+ bool marsOn)
{
SqlErrorCollection errors = new SqlErrorCollection();
errors.Add(new SqlError(TdsEnums.TIMEOUT_EXPIRED, (byte)0x00, TdsEnums.MIN_ERROR_CLASS, null, (StringsHelper.GetString(Strings.ADP_OpenReaderExists, marsOn ? ADP.Command : ADP.Connection)), "", 0, TdsEnums.SNI_WAIT_TIMEOUT));
@@ -1110,7 +1113,9 @@ internal static Exception UnsupportedSysTxForGlobalTransactions()
/// * server-provided failover partner - raising SqlException in this case
/// * connection string with failover partner and MultiSubnetFailover=true - raising argument one in this case with the same message
///
- internal static Exception MultiSubnetFailoverWithFailoverPartner(bool serverProvidedFailoverPartner, SqlInternalConnectionTds internalConnection)
+ internal static Exception MultiSubnetFailoverWithFailoverPartner(
+ bool serverProvidedFailoverPartner,
+ SqlConnectionInternal internalConnection)
{
string msg = StringsHelper.GetString(Strings.SQLMSF_FailoverPartnerNotSupported);
if (serverProvidedFailoverPartner)
@@ -1154,7 +1159,7 @@ internal static Exception ROR_FailoverNotSupportedConnString()
return ADP.Argument(StringsHelper.GetString(Strings.SQLROR_FailoverNotSupported));
}
- internal static Exception ROR_FailoverNotSupportedServer(SqlInternalConnectionTds internalConnection)
+ internal static Exception ROR_FailoverNotSupportedServer(SqlConnectionInternal internalConnection)
{
SqlErrorCollection errors = new SqlErrorCollection();
errors.Add(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, null, (StringsHelper.GetString(Strings.SQLROR_FailoverNotSupported)), "", 0));
@@ -1163,7 +1168,7 @@ internal static Exception ROR_FailoverNotSupportedServer(SqlInternalConnectionTd
return exc;
}
- internal static Exception ROR_RecursiveRoutingNotSupported(SqlInternalConnectionTds internalConnection, int maxNumberOfRedirectRoute)
+ internal static Exception ROR_RecursiveRoutingNotSupported(SqlConnectionInternal internalConnection, int maxNumberOfRedirectRoute)
{
SqlErrorCollection errors = new SqlErrorCollection();
errors.Add(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, null, (StringsHelper.GetString(Strings.SQLROR_RecursiveRoutingNotSupported, maxNumberOfRedirectRoute)), "", 0));
@@ -1172,7 +1177,7 @@ internal static Exception ROR_RecursiveRoutingNotSupported(SqlInternalConnection
return exc;
}
- internal static Exception ROR_InvalidRoutingInfo(SqlInternalConnectionTds internalConnection)
+ internal static Exception ROR_InvalidRoutingInfo(SqlConnectionInternal internalConnection)
{
SqlErrorCollection errors = new SqlErrorCollection();
errors.Add(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, null, (StringsHelper.GetString(Strings.SQLROR_InvalidRoutingInfo)), "", 0));
@@ -1181,7 +1186,7 @@ internal static Exception ROR_InvalidRoutingInfo(SqlInternalConnectionTds intern
return exc;
}
- internal static Exception ROR_TimeoutAfterRoutingInfo(SqlInternalConnectionTds internalConnection)
+ internal static Exception ROR_TimeoutAfterRoutingInfo(SqlConnectionInternal internalConnection)
{
SqlErrorCollection errors = new SqlErrorCollection();
errors.Add(new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, null, (StringsHelper.GetString(Strings.SQLROR_TimeoutAfterRoutingInfo)), "", 0));
@@ -1221,7 +1226,7 @@ internal static Exception CR_NextAttemptWillExceedQueryTimeout(SqlException inne
return exc;
}
- internal static Exception CR_EncryptionChanged(SqlInternalConnectionTds internalConnection)
+ internal static Exception CR_EncryptionChanged(SqlConnectionInternal internalConnection)
{
SqlErrorCollection errors = new SqlErrorCollection();
errors.Add(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, null, StringsHelper.GetString(Strings.SQLCR_EncryptionChanged), "", 0));
@@ -1237,7 +1242,7 @@ internal static SqlException CR_AllAttemptsFailed(SqlException innerException, G
return exc;
}
- internal static SqlException CR_NoCRAckAtReconnection(SqlInternalConnectionTds internalConnection)
+ internal static SqlException CR_NoCRAckAtReconnection(SqlConnectionInternal internalConnection)
{
SqlErrorCollection errors = new SqlErrorCollection();
errors.Add(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, null, StringsHelper.GetString(Strings.SQLCR_NoCRAckAtReconnection), "", 0));
@@ -1245,7 +1250,7 @@ internal static SqlException CR_NoCRAckAtReconnection(SqlInternalConnectionTds i
return exc;
}
- internal static SqlException CR_TDSVersionNotPreserved(SqlInternalConnectionTds internalConnection)
+ internal static SqlException CR_TDSVersionNotPreserved(SqlConnectionInternal internalConnection)
{
SqlErrorCollection errors = new SqlErrorCollection();
errors.Add(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, null, StringsHelper.GetString(Strings.SQLCR_TDSVersionNotPreserved), "", 0));
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
index 115c62f6c5..07ab80b96a 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -101,7 +101,7 @@ internal sealed partial class TdsParser
private int _nonTransactedOpenResultCount = 0;
// Connection reference
- private SqlInternalConnectionTds _connHandler;
+ private SqlConnectionInternal _connHandler;
// Async/Mars variables
private bool _fMARS = false;
@@ -209,7 +209,7 @@ internal TdsParser(bool MARS, bool fAsynchronous)
DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED;
}
- internal SqlInternalConnectionTds Connection
+ internal SqlConnectionInternal Connection
{
get
{
@@ -359,17 +359,19 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj)
}
}
- internal void Connect(ServerInfo serverInfo,
- SqlInternalConnectionTds connHandler,
- TimeoutTimer timeout,
- SqlConnectionString connectionOptions,
-#if NETFRAMEWORK
- bool withFailover,
- bool isFirstTransparentAttempt,
- bool disableTnir
-#else
- bool withFailover
-#endif
+ internal void Connect(
+ ServerInfo serverInfo,
+ SqlConnectionInternal connHandler,
+ TimeoutTimer timeout,
+ SqlConnectionString connectionOptions,
+
+ #if NETFRAMEWORK
+ bool withFailover,
+ bool isFirstTransparentAttempt,
+ bool disableTnir
+ #else
+ bool withFailover
+ #endif
)
{
SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt;
@@ -1689,7 +1691,7 @@ internal void ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand
if (asyncClose)
{
// Wait until we have the parser lock, then try to close
- SqlInternalConnectionTds connHandler = _connHandler;
+ SqlConnectionInternal connHandler = _connHandler;
Action wrapCloseAction = closeAction =>
{
Task.Factory.StartNew(() =>
@@ -6236,7 +6238,9 @@ private TdsOperationStatus TryProcessRow(_SqlMetaDataSet columns, object[] buffe
/// Determines if a column value should be transparently decrypted (based on SqlCommand and Connection String settings).
///
/// true if the value should be transparently decrypted, false otherwise
- internal static bool ShouldHonorTceForRead(SqlCommandColumnEncryptionSetting columnEncryptionSetting, SqlInternalConnectionTds connection)
+ internal static bool ShouldHonorTceForRead(
+ SqlCommandColumnEncryptionSetting columnEncryptionSetting,
+ SqlConnectionInternal connection)
{
// Command leve setting trumps all
switch (columnEncryptionSetting)
@@ -6259,7 +6263,7 @@ internal static bool ShouldHonorTceForRead(SqlCommandColumnEncryptionSetting col
internal static object GetNullSqlValue(SqlBuffer nullVal,
SqlMetaDataPriv md,
SqlCommandColumnEncryptionSetting columnEncryptionSetting,
- SqlInternalConnectionTds connection)
+ SqlConnectionInternal connection)
{
SqlDbType type = md.type;
@@ -10000,10 +10004,10 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques
static (Task task, object state) =>
{
Debug.Assert(!task.IsCanceled, "Task should not be canceled");
- var parameters = (Tuple)state;
+ var parameters = (Tuple)state;
TdsParser parser = parameters.Item1;
TdsParserStateObject tdsParserStateObject = parameters.Item2;
- SqlInternalConnectionTds internalConnectionTds = parameters.Item3;
+ SqlConnectionInternal internalConnectionTds = parameters.Item3;
try
{
if (task.IsFaulted)
@@ -10237,7 +10241,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout
if (releaseConnectionLock)
{
task.ContinueWith(
- static (Task _, object state) => ((SqlInternalConnectionTds)state)._parserLock.Release(),
+ static (Task _, object state) => ((SqlConnectionInternal)state)._parserLock.Release(),
state: _connHandler,
TaskScheduler.Default
);
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs
index dfc37d2720..435d9cd956 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs
@@ -4,7 +4,7 @@
using System;
using System.Data;
-using Microsoft.Data.SqlClient;
+using Microsoft.Data.SqlClient.Connection;
using Microsoft.Data.SqlClient.Tests.Common;
using Microsoft.SqlServer.TDS.Servers;
using Xunit;
@@ -563,7 +563,7 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs
// Connect once to the primary to trigger it to send the failover partner
connection.Open();
- Assert.Equal("invalidhost", (connection.InnerConnection as SqlInternalConnectionTds)!.ServerProvidedFailoverPartner);
+ Assert.Equal("invalidhost", (connection.InnerConnection as SqlConnectionInternal)!.ServerProvidedFailoverPartner);
// Close the connection to return it to the pool
connection.Close();