diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index a07133eb07..1e038fdc5a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -919,6 +919,9 @@ public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(System.Collect public int CommandTimeout { get { throw null; } } /// [System.ComponentModel.DefaultValueAttribute("")] +#pragma warning disable CS0618 + [System.ComponentModel.RecommendedAsConfigurableAttribute(true)] +#pragma warning restore CS0618 [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] [System.ComponentModel.SettingsBindableAttribute(true)] public override string ConnectionString { get { throw null; } set { } } 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 808c0ea690..bd92be1a2a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -579,6 +579,9 @@ Microsoft\Data\SqlClient\SqlCommandSet.cs + + Microsoft\Data\SqlClient\SqlConnection.cs + Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs @@ -812,7 +815,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 2e5398bc21..0008c37f68 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -800,6 +800,9 @@ public SqlConnection(string connectionString, Microsoft.Data.SqlClient.SqlCreden public int CommandTimeout { get { throw null; } } /// [System.ComponentModel.DefaultValueAttribute("")] +#pragma warning disable CS0618 + [System.ComponentModel.RecommendedAsConfigurableAttribute(true)] +#pragma warning restore CS0618 [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] [System.ComponentModel.SettingsBindableAttribute(true)] public override string ConnectionString { get { throw null; } set { } } 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 522cb63dc7..8fdd648ee5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -750,6 +750,9 @@ Microsoft\Data\SqlClient\SqlCommandSet.cs + + Microsoft\Data\SqlClient\SqlConnection.cs + Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs @@ -980,7 +983,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs deleted file mode 100644 index 8dc9a7a7cc..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ /dev/null @@ -1,2544 +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.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Data.Common; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.ConstrainedExecution; -using System.Security; -using System.Security.Permissions; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Data.Common; -using Microsoft.Data.Common.ConnectionString; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient.ConnectionPool; -using Microsoft.Data.SqlClient.Diagnostics; -using Microsoft.SqlServer.Server; - -[assembly: InternalsVisibleTo("System.Data.DataSetExtensions, PublicKey=" + Microsoft.Data.SqlClient.AssemblyRef.EcmaPublicKeyFull)] // DevDiv Bugs 92166 -// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available. -// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future. -namespace Microsoft.Data.SqlClient -{ - /// - [DefaultEvent("InfoMessage")] - [DesignerCategory("")] - public sealed partial class SqlConnection : DbConnection, ICloneable - { - static private readonly object EventInfoMessage = new object(); - - private bool _AsyncCommandInProgress; - - // SQLStatistics support - internal SqlStatistics _statistics; - private bool _collectstats; - - private bool _fireInfoMessageEventOnUserErrors; // False by default - - // root task associated with current async invocation - private Tuple, Task> _currentCompletion; - - private SqlCredential _credential; - private string _connectionString; - private int _connectRetryCount; - private string _accessToken; // Access Token to be used for token based authentication - - // connection resiliency - private object _reconnectLock = new object(); - internal Task _currentReconnectionTask; - private Task _asyncWaitingForReconnection; // current async task waiting for reconnection in non-MARS connections - private Guid _originalConnectionId = Guid.Empty; - private CancellationTokenSource _reconnectionCancellationSource; - internal SessionData _recoverySessionData; - internal bool _suppressStateChangeForReconnection = false; // Do not use for anything else ! Value will be overwritten by CR process - internal WindowsIdentity _lastIdentity; - internal WindowsIdentity _impersonateIdentity; - private int _reconnectCount; - - // Retry Logic - private SqlRetryLogicBaseProvider _retryLogicProvider; - - // diagnostics listener - private static readonly SqlDiagnosticListener s_diagnosticListener = new(); - - // Transient Fault handling flag. This is needed to convey to the downstream mechanism of connection establishment, if Transient Fault handling should be used or not - // The downstream handling of Connection open is the same for idle connection resiliency. Currently we want to apply transient fault handling only to the connections opened - // using SqlConnection.Open() method. - internal bool _applyTransientFaultHandling = false; - - // System column encryption key store providers are added by default - private static readonly Dictionary s_systemColumnEncryptionKeyStoreProviders - = new(capacity: 3, comparer: StringComparer.OrdinalIgnoreCase) - { - { SqlColumnEncryptionCertificateStoreProvider.ProviderName, new SqlColumnEncryptionCertificateStoreProvider() }, - { SqlColumnEncryptionCngProvider.ProviderName, new SqlColumnEncryptionCngProvider() }, - { SqlColumnEncryptionCspProvider.ProviderName, new SqlColumnEncryptionCspProvider() } - }; - - /// Instance-level list of custom key store providers. It can be set more than once by the user. - private IReadOnlyDictionary _customColumnEncryptionKeyStoreProviders; - - private Func> _accessTokenCallback; - - private SspiContextProvider _sspiContextProvider; - - internal bool HasColumnEncryptionKeyStoreProvidersRegistered => - _customColumnEncryptionKeyStoreProviders is not null && _customColumnEncryptionKeyStoreProviders.Count > 0; - - // Lock to control setting of s_globalCustomColumnEncryptionKeyStoreProviders - private static readonly object s_globalCustomColumnEncryptionKeyProvidersLock = new(); - - /// - /// Global custom provider list should be provided by the user. We shallow copy the user supplied dictionary into a ReadOnlyDictionary. - /// Global custom provider list can only be supplied once per application. - /// - private static IReadOnlyDictionary s_globalCustomColumnEncryptionKeyStoreProviders; - - /// - /// Dictionary object holding trusted key paths for various SQL Servers. - /// Key to the dictionary is a SQL Server Name - /// IList contains a list of trusted key paths. - /// - private static readonly ConcurrentDictionary> _ColumnEncryptionTrustedMasterKeyPaths - = new(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, - capacity: 1, - comparer: StringComparer.OrdinalIgnoreCase); - - private static readonly Action, object> s_openAsyncComplete = OpenAsyncComplete; - - private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider); - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public SqlRetryLogicBaseProvider RetryLogicProvider - { - get - { - if (_retryLogicProvider == null) - { - _retryLogicProvider = SqlConfigurableRetryLogicManager.ConnectionProvider; - } - return _retryLogicProvider; - } - set - { - _retryLogicProvider = value; - } - } - - /// - [DefaultValue(null)] - [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_ColumnEncryptionKeyCacheTtl)] - public static TimeSpan ColumnEncryptionKeyCacheTtl { get; set; } = TimeSpan.FromHours(2); - - /// - [DefaultValue(null)] - [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled)] - public static bool ColumnEncryptionQueryMetadataCacheEnabled { get; set; } = true; - - /// - [DefaultValue(null)] - [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_TrustedColumnMasterKeyPaths)] - public static IDictionary> ColumnEncryptionTrustedMasterKeyPaths => _ColumnEncryptionTrustedMasterKeyPaths; - - /// - public SqlConnection(string connectionString) : this(connectionString, null) - { - } - - /// - public SqlConnection(string connectionString, SqlCredential credential) : this() - { - ConnectionString = connectionString; // setting connection string first so that ConnectionOption is available - if (credential != null) - { - // The following checks are necessary as setting Credential property will call CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential - // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential it will throw InvalidOperationException rather than ArgumentException - // Need to call setter on Credential property rather than setting _credential directly as pool groups need to be checked - SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions; - if (UsesClearUserIdOrPassword(connectionOptions)) - { - throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); - } - - if (UsesIntegratedSecurity(connectionOptions)) - { - throw ADP.InvalidMixedArgumentOfSecureCredentialAndIntegratedSecurity(); - } - else if (UsesActiveDirectoryIntegrated(connectionOptions)) - { - throw SQL.SettingCredentialWithIntegratedArgument(); - } - else if (UsesActiveDirectoryInteractive(connectionOptions)) - { - throw SQL.SettingCredentialWithInteractiveArgument(); - } - else if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) - { - throw SQL.SettingCredentialWithDeviceFlowArgument(); - } - else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); - } - else if (UsesActiveDirectoryMSI(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryMSIString); - } - else if (UsesActiveDirectoryDefault(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryDefaultString); - } - else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); - } - - Credential = credential; - } - // else - // credential == null: we should not set "Credential" as this will do additional validation check and - // checking pool groups which is not necessary. All necessary operation is already done by calling "ConnectionString = connectionString" - } - - private SqlConnection(SqlConnection connection) - { - GC.SuppressFinalize(this); - CopyFrom(connection); - _connectionString = connection._connectionString; - if (connection._credential != null) - { - SecureString password = connection._credential.Password.Copy(); - password.MakeReadOnly(); - _credential = new SqlCredential(connection._credential.UserId, password); - } - - _accessToken = connection._accessToken; - _accessTokenCallback = connection._accessTokenCallback; - CacheConnectionStringProperties(); - } - - internal static bool TryGetSystemColumnEncryptionKeyStoreProvider(string keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider) - { - return s_systemColumnEncryptionKeyStoreProviders.TryGetValue(keyStoreName, out provider); - } - - /// - /// This function walks through both instance-level and global custom column encryption key store providers and returns an object if found. - /// - /// Provider Name to be searched for. - /// If the provider is found, initializes the corresponding SqlColumnEncryptionKeyStoreProvider instance. - /// true if the provider is found, else returns false - internal bool TryGetColumnEncryptionKeyStoreProvider(string providerName, out SqlColumnEncryptionKeyStoreProvider columnKeyStoreProvider) - { - Debug.Assert(!string.IsNullOrWhiteSpace(providerName), "Provider name is invalid"); - - if (HasColumnEncryptionKeyStoreProvidersRegistered) - { - return _customColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider); - } - - lock (s_globalCustomColumnEncryptionKeyProvidersLock) - { - // If custom provider is not set, then return false - if (s_globalCustomColumnEncryptionKeyStoreProviders is null) - { - columnKeyStoreProvider = null; - return false; - } - - // Search in the custom provider list - return s_globalCustomColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider); - } - } - - /// - /// This function returns a list of system providers currently supported by this driver. - /// - /// Combined list of provider names - internal static List GetColumnEncryptionSystemKeyStoreProvidersNames() - { - if (s_systemColumnEncryptionKeyStoreProviders.Count > 0) - { - return new List(s_systemColumnEncryptionKeyStoreProviders.Keys); - } - return new List(0); - } - - /// - /// This function returns a list of the names of the custom providers currently registered. If the - /// instance-level cache is not empty, that cache is used, else the global cache is used. - /// - /// Combined list of provider names - internal List GetColumnEncryptionCustomKeyStoreProvidersNames() - { - if (_customColumnEncryptionKeyStoreProviders is not null && - _customColumnEncryptionKeyStoreProviders.Count > 0) - { - return new List(_customColumnEncryptionKeyStoreProviders.Keys); - } - if (s_globalCustomColumnEncryptionKeyStoreProviders is not null) - { - return new List(s_globalCustomColumnEncryptionKeyStoreProviders.Keys); - } - return new List(0); - } - - /// - /// Is this connection using column encryption ? - /// - internal bool IsColumnEncryptionSettingEnabled - { - get - { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return opt?.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled; - } - } - - /// - public static void RegisterColumnEncryptionKeyStoreProviders(IDictionary customProviders) - { - ValidateCustomProviders(customProviders); - - lock (s_globalCustomColumnEncryptionKeyProvidersLock) - { - // Provider list can only be set once - if (s_globalCustomColumnEncryptionKeyStoreProviders is not null) - { - throw SQL.CanOnlyCallOnce(); - } - - // AKV provider registration supports multi-user scenarios, so it is not safe to cache the CEK in the global provider. - // The CEK cache is a global cache, and is shared across all connections. - // To prevent conflicts between CEK caches, global providers should not use their own CEK caches - foreach (SqlColumnEncryptionKeyStoreProvider provider in customProviders.Values) - { - provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0); - } - - // Create a temporary dictionary and then add items from the provided dictionary. - // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs - // in the provided customerProviders dictionary. - Dictionary customColumnEncryptionKeyStoreProviders = - new(customProviders, StringComparer.OrdinalIgnoreCase); - - // Set the dictionary to the ReadOnly dictionary. - s_globalCustomColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders; - } - } - - /// - public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(IDictionary customProviders) - { - ValidateCustomProviders(customProviders); - - // Create a temporary dictionary and then add items from the provided dictionary. - // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs - // in the provided customerProviders dictionary. - Dictionary customColumnEncryptionKeyStoreProviders = - new(customProviders, StringComparer.OrdinalIgnoreCase); - - // Set the dictionary to the ReadOnly dictionary. - // This method can be called more than once. Re-registering a new collection will replace the - // old collection of providers. - _customColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders; - } - - private static void ValidateCustomProviders(IDictionary customProviders) - { - // Throw when the provided dictionary is null. - if (customProviders is null) - { - throw SQL.NullCustomKeyStoreProviderDictionary(); - } - - // Validate that custom provider list doesn't contain any of system provider list - foreach (string key in customProviders.Keys) - { - // Validate the provider name - // Check for null or empty - if (string.IsNullOrWhiteSpace(key)) - { - throw SQL.EmptyProviderName(); - } - - // Check if the name starts with MSSQL_, since this is reserved namespace for system providers. - if (key.StartsWith(ADP.ColumnEncryptionSystemProviderNamePrefix, StringComparison.InvariantCultureIgnoreCase)) - { - throw SQL.InvalidCustomKeyStoreProviderName(key, ADP.ColumnEncryptionSystemProviderNamePrefix); - } - - // Validate the provider value - if (customProviders[key] is null) - { - throw SQL.NullProviderValue(key); - } - } - } - - /// - /// Get enclave attestation url to be used with enclave based Always Encrypted - /// - internal string EnclaveAttestationUrl - { - get => ((SqlConnectionString)ConnectionOptions).EnclaveAttestationUrl; - } - - /// - /// Get attestation protocol - /// - internal SqlConnectionAttestationProtocol AttestationProtocol - { - get => ((SqlConnectionString)ConnectionOptions).AttestationProtocol; - } - - /// - /// Get IP address preference - /// - internal SqlConnectionIPAddressPreference iPAddressPreference - { - get => ((SqlConnectionString)ConnectionOptions).IPAddressPreference; - } - - // This method will be called once connection string is set or changed. - private void CacheConnectionStringProperties() - { - SqlConnectionString connString = ConnectionOptions as SqlConnectionString; - if (connString != null) - { - _connectRetryCount = connString.ConnectRetryCount; - // For Azure Synapse ondemand connections, set _connectRetryCount to 5 instead of 1 to greatly improve recovery - // success rate. Note: Synapse should be detected first as it could be detected as a regular Azure SQL DB endpoint. - if (_connectRetryCount == 1 && ADP.IsAzureSynapseOnDemandEndpoint(connString.DataSource)) - { - _connectRetryCount = 5; - } - // For Azure SQL connection, set _connectRetryCount to 2 instead of 1 will greatly improve recovery - // success rate - else if (_connectRetryCount == 1 && ADP.IsAzureSqlServerEndpoint(connString.DataSource)) - { - _connectRetryCount = 2; - } - } - } - - // - // PUBLIC PROPERTIES - // - - /// - // used to start/stop collection of statistics data and do verify the current state - // - // devnote: start/stop should not performed using a property since it requires execution of code - // - // start statistics - // set the internal flag (_statisticsEnabled) to true. - // Create a new SqlStatistics object if not already there. - // connect the parser to the object. - // if there is no parser at this time we need to connect it after creation. - [DefaultValue(false)] - [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_StatisticsEnabled)] - public bool StatisticsEnabled - { - get - { - return (_collectstats); - } - set - { - if (value) - { - // start - if (ConnectionState.Open == State) - { - if (_statistics == null) - { - _statistics = new SqlStatistics(); - _statistics._openTimestamp = ADP.TimerCurrent(); - } - // set statistics on the parser - // update timestamp; - Debug.Assert(Parser != null, "Where's the parser?"); - Parser.Statistics = _statistics; - } - } - else - { - // stop - if (_statistics != null) - { - if (ConnectionState.Open == State) - { - // remove statistics from parser - // update timestamp; - TdsParser parser = Parser; - Debug.Assert(parser != null, "Where's the parser?"); - parser.Statistics = null; - _statistics._closeTimestamp = ADP.TimerCurrent(); - } - } - } - _collectstats = value; - } - } - - internal bool AsyncCommandInProgress - { - get => _AsyncCommandInProgress; - set => _AsyncCommandInProgress = value; - } - - private bool UsesActiveDirectoryIntegrated(SqlConnectionString opt) - { - return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated; - } - - private bool UsesActiveDirectoryInteractive(SqlConnectionString opt) - { - return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive; - } - - private bool UsesActiveDirectoryDeviceCodeFlow(SqlConnectionString opt) - { - return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; - } - - private bool UsesActiveDirectoryManagedIdentity(SqlConnectionString opt) - { - return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity; - } - - private bool UsesActiveDirectoryMSI(SqlConnectionString opt) - { - return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI; - } - - private bool UsesActiveDirectoryDefault(SqlConnectionString opt) - { - return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault; - } - - private bool UsesActiveDirectoryWorkloadIdentity(SqlConnectionString opt) - { - return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; - } - - private bool UsesAuthentication(SqlConnectionString opt) - { - return opt != null && opt.Authentication != SqlAuthenticationMethod.NotSpecified; - } - - // Does this connection use Integrated Security? - private bool UsesIntegratedSecurity(SqlConnectionString opt) - { - return opt != null && opt.IntegratedSecurity; - } - - // Does this connection use old style of clear userID or Password in connection string? - private bool UsesClearUserIdOrPassword(SqlConnectionString opt) - { - bool result = false; - if (opt != null) - { - result = (!string.IsNullOrEmpty(opt.UserID) || !string.IsNullOrEmpty(opt.Password)); - } - return result; - } - - internal SqlConnectionString.TransactionBindingEnum TransactionBinding - { - get => ((SqlConnectionString)ConnectionOptions).TransactionBinding; - } - - internal SqlConnectionString.TypeSystem TypeSystem - { - get => ((SqlConnectionString)ConnectionOptions).TypeSystemVersion; - } - - internal Version TypeSystemAssemblyVersion - { - get => ((SqlConnectionString)ConnectionOptions).TypeSystemAssemblyVersion; - } - - internal int ConnectRetryInterval - { - get => ((SqlConnectionString)ConnectionOptions).ConnectRetryInterval; - } - - internal SspiContextProvider SspiContextProvider - { - get { return _sspiContextProvider; } - set - { - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, accessToken: null, accessTokenCallback: null, sspiContextProvider: value)); - _sspiContextProvider = value; - } - } - - /// - [DefaultValue("")] -#pragma warning disable 618 // ignore obsolete warning about RecommendedAsConfigurable to use SettingsBindableAttribute - [RecommendedAsConfigurable(true)] -#pragma warning restore 618 - [SettingsBindableAttribute(true)] - [RefreshProperties(RefreshProperties.All)] - [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionString)] - public override string ConnectionString - { - get - { - return ConnectionString_Get(); - } - set - { - if (_credential != null || _accessToken != null || _accessTokenCallback != null) - { - SqlConnectionString connectionOptions = new SqlConnectionString(value); - if (_credential != null) - { - // Check for Credential being used with Authentication=ActiveDirectoryIntegrated | ActiveDirectoryInteractive | - // ActiveDirectoryDeviceCodeFlow | ActiveDirectoryManagedIdentity/ActiveDirectoryMSI | ActiveDirectoryDefault. Since a different error string is used - // for this case in ConnectionString setter vs in Credential setter, check for this error case before calling - // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential, which is common to both setters. - if (UsesActiveDirectoryIntegrated(connectionOptions)) - { - throw SQL.SettingIntegratedWithCredential(); - } - else if (UsesActiveDirectoryInteractive(connectionOptions)) - { - throw SQL.SettingInteractiveWithCredential(); - } - else if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) - { - throw SQL.SettingDeviceFlowWithCredential(); - } - else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) - { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); - } - else if (UsesActiveDirectoryMSI(connectionOptions)) - { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryMSIString); - } - else if (UsesActiveDirectoryDefault(connectionOptions)) - { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryDefaultString); - } - else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) - { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); - } - - CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); - } - - if (_accessToken != null) - { - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(connectionOptions); - } - - if (_accessTokenCallback != null) - { - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback(connectionOptions); - } - } - ConnectionString_Set(new SqlConnectionPoolKey(value, _credential, _accessToken, _accessTokenCallback, _sspiContextProvider)); - _connectionString = value; // Change _connectionString value only after value is validated - CacheConnectionStringProperties(); - } - } - - /// - [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public override int ConnectionTimeout - { - get - { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - return constr != null ? constr.ConnectTimeout : DbConnectionStringDefaults.ConnectTimeout; - } - } - - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout)] - public int CommandTimeout - { - get - { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - return constr != null ? constr.CommandTimeout : DbConnectionStringDefaults.CommandTimeout; - } - } - - /// - // AccessToken: To be used for token based authentication - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_AccessToken)] - public string AccessToken - { - get - { - // When a connection is connecting or is ever opened, make AccessToken available only if "Persist Security Info" is set to true - // otherwise, return null - SqlConnectionString connectionOptions = (SqlConnectionString)UserConnectionOptions; - return InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo ? null : _accessToken; - } - set - { - // If a connection is connecting or is ever opened, AccessToken cannot be set - if (!InnerConnection.AllowSetConnectionString) - { - throw ADP.OpenConnectionPropertySet("AccessToken", InnerConnection.State); - } - - if (value != null) - { - // Check if the usage of AccessToken has any conflict with the keys used in connection string and credential - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken((SqlConnectionString)ConnectionOptions); - } - - _accessToken = value; - // Need to call ConnectionString_Set to do proper pool group check - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, _accessToken, null, sspiContextProvider: null)); - } - } - - /// - public Func> AccessTokenCallback - { - get { return _accessTokenCallback; } - set - { - // If a connection is connecting or is ever opened, AccessToken callback cannot be set - if (!InnerConnection.AllowSetConnectionString) - { - throw ADP.OpenConnectionPropertySet(nameof(AccessTokenCallback), InnerConnection.State); - } - - if (value != null) - { - // Check if the usage of AccessToken has any conflict with the keys used in connection string and credential - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback((SqlConnectionString)ConnectionOptions); - } - - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, null, value, sspiContextProvider: null)); - _accessTokenCallback = value; - } - } - - /// - [ResDescription(StringsHelper.ResourceNames.SqlConnection_Database)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public override string Database - { - // if the connection is open, we need to ask the inner connection what it's - // current catalog is because it may have gotten changed, otherwise we can - // just return what the connection string had. - get - { - SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection); - string result; - - if (innerConnection != null) - { - result = innerConnection.CurrentDatabase; - } - else - { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - result = constr != null ? constr.InitialCatalog : DbConnectionStringDefaults.InitialCatalog; - } - return result; - } - } - - /// - /// To indicate the IsSupported flag sent by the server for DNS Caching. This property is for internal testing only. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - internal string SQLDNSCachingSupportedState - { - get - { - SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); - string result; - - if (innerConnection != null) - { - result = innerConnection.IsSQLDNSCachingSupported ? "true" : "false"; - } - else - { - result = "innerConnection is null!"; - } - - return result; - } - } - - /// - /// To indicate the IsSupported flag sent by the server for DNS Caching before redirection. This property is for internal testing only. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - internal string SQLDNSCachingSupportedStateBeforeRedirect - { - get - { - SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); - string result; - - if (innerConnection != null) - { - result = innerConnection.IsDNSCachingBeforeRedirectSupported ? "true" : "false"; - } - else - { - result = "innerConnection is null!"; - } - - return result; - } - } - - /// - [Browsable(true)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_DataSource)] - public override string DataSource - { - get - { - SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection); - string result; - - if (innerConnection != null) - { - result = innerConnection.CurrentDataSource; - } - else - { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - result = constr != null ? constr.DataSource : DbConnectionStringDefaults.DataSource; - } - return result; - } - } - - /// - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_PacketSize)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int PacketSize - { - // if the connection is open, we need to ask the inner connection what it's - // current packet size is because it may have gotten changed, otherwise we - // can just return what the connection string had. - get - { - int result; - if (InnerConnection is SqlInternalConnectionTds innerConnection) - { - result = innerConnection.PacketSize; - } - else - { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - result = constr != null ? constr.PacketSize : DbConnectionStringDefaults.PacketSize; - } - - return result; - } - } - - /// - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_ClientConnectionId)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public Guid ClientConnectionId - { - get - { - SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); - - if (innerConnection != null) - { - return innerConnection.ClientConnectionId; - } - else - { - Task reconnectTask = _currentReconnectionTask; - // Connection closed but previously open should return the correct ClientConnectionId - DbConnectionClosedPreviouslyOpened innerConnectionClosed = (InnerConnection as DbConnectionClosedPreviouslyOpened); - if ((reconnectTask != null && !reconnectTask.IsCompleted) || innerConnectionClosed != null) - { - return _originalConnectionId; - } - return Guid.Empty; - } - } - } - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_ServerVersion)] - public override string ServerVersion - { - get - { - return GetOpenConnection().ServerVersion; - } - } - - /// - [Browsable(false)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_ServerProcessId)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int ServerProcessId - { - get => State.Equals(ConnectionState.Open) | State.Equals(ConnectionState.Executing) | State.Equals(ConnectionState.Fetching) ? - GetOpenTdsConnection().ServerProcessId : 0; - } - - /// - [Browsable(false)] - [ResDescription(StringsHelper.ResourceNames.DbConnection_State)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public override ConnectionState State - { - get - { - Task reconnectTask = _currentReconnectionTask; - if (reconnectTask != null && !reconnectTask.IsCompleted) - { - return ConnectionState.Open; - } - return InnerConnection.State; - } - } - - - internal SqlStatistics Statistics - { - get => _statistics; - } - - /// - [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_WorkstationId)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public string WorkstationId - { - get - { - // If not supplied by the user, the default value is the MachineName - // Note: In Longhorn you'll be able to rename a machine without - // rebooting. Therefore, don't cache this machine name. - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - string result = constr != null ? constr.WorkstationId : null; - if (result == null) - { - // getting machine name requires Environment.Permission - // user must have that permission in order to retrieve this - result = Environment.MachineName; - } - return result; - } - } - - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - [ResDescription(StringsHelper.ResourceNames.SqlConnection_Credential)] - public SqlCredential Credential - { - get - { - SqlCredential result = _credential; - - // When a connection is connecting or is ever opened, make credential available only if "Persist Security Info" is set to true - // otherwise, return null - SqlConnectionString connectionOptions = (SqlConnectionString)UserConnectionOptions; - if (InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo) - { - result = null; - } - - return result; - } - - set - { - // If a connection is connecting or is ever opened, user id/password cannot be set - if (!InnerConnection.AllowSetConnectionString) - { - throw ADP.OpenConnectionPropertySet(nameof(Credential), InnerConnection.State); - } - - // check if the usage of credential has any conflict with the keys used in connection string - if (value != null) - { - var connectionOptions = (SqlConnectionString)ConnectionOptions; - // Check for Credential being used with Authentication=ActiveDirectoryIntegrated | ActiveDirectoryInteractive | - // ActiveDirectoryDeviceCodeFlow | ActiveDirectoryManagedIdentity/ActiveDirectoryMSI | ActiveDirectoryDefault. Since a different error string is used - // for this case in ConnectionString setter vs in Credential setter, check for this error case before calling - // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential, which is common to both setters. - if (UsesActiveDirectoryIntegrated(connectionOptions)) - { - throw SQL.SettingCredentialWithIntegratedInvalid(); - } - else if (UsesActiveDirectoryInteractive(connectionOptions)) - { - throw SQL.SettingCredentialWithInteractiveInvalid(); - } - else if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) - { - throw SQL.SettingCredentialWithDeviceFlowInvalid(); - } - else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); - } - else if (UsesActiveDirectoryMSI(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryMSIString); - } - else if (UsesActiveDirectoryDefault(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryDefaultString); - } - else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); - } - - CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); - - if (_accessToken != null) - { - throw ADP.InvalidMixedUsageOfCredentialAndAccessToken(); - } - } - - _credential = value; - - // Need to call ConnectionString_Set to do proper pool group check - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, _accessToken, _accessTokenCallback, sspiContextProvider: null)); - } - } - - // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential: check if the usage of credential has any conflict - // with the keys used in connection string - // If there is any conflict, it throws InvalidOperationException - // This is used in the setter of ConnectionString and Credential properties. - private void CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(SqlConnectionString connectionOptions) - { - if (UsesClearUserIdOrPassword(connectionOptions)) - { - throw ADP.InvalidMixedUsageOfSecureAndClearCredential(); - } - - if (UsesIntegratedSecurity(connectionOptions)) - { - throw ADP.InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity(); - } - } - - // CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken: check if the usage of AccessToken has any conflict - // with the keys used in connection string and credential - // If there is any conflict, it throws InvalidOperationException - // This is to be used setter of ConnectionString and AccessToken properties - private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(SqlConnectionString connectionOptions) - { - if (UsesClearUserIdOrPassword(connectionOptions)) - { - throw ADP.InvalidMixedUsageOfAccessTokenAndUserIDPassword(); - } - - if (UsesIntegratedSecurity(connectionOptions)) - { - throw ADP.InvalidMixedUsageOfAccessTokenAndIntegratedSecurity(); - } - - if (UsesAuthentication(connectionOptions)) - { - throw ADP.InvalidMixedUsageOfAccessTokenAndAuthentication(); - } - - // Check if the usage of AccessToken has the conflict with credential - if (_credential != null) - { - throw ADP.InvalidMixedUsageOfAccessTokenAndCredential(); - } - - if (_accessTokenCallback != null) - { - throw ADP.InvalidMixedUsageOfAccessTokenAndTokenCallback(); - } - } - - // CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback: check if the usage of AccessTokenCallback has any conflict - // with the keys used in connection string and credential - // If there is any conflict, it throws InvalidOperationException - // This is to be used setter of ConnectionString and AccessTokenCallback properties - private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback(SqlConnectionString connectionOptions) - { - if (UsesIntegratedSecurity(connectionOptions)) - { - throw ADP.InvalidMixedUsageOfAccessTokenCallbackAndIntegratedSecurity(); - } - - if (UsesAuthentication(connectionOptions)) - { - throw ADP.InvalidMixedUsageOfAccessTokenCallbackAndAuthentication(); - } - - if (_accessToken != null) - { - throw ADP.InvalidMixedUsageOfAccessTokenAndTokenCallback(); - } - } - - /// - protected override DbProviderFactory DbProviderFactory - { - get => SqlClientFactory.Instance; - } - - // - // PUBLIC EVENTS - // - - /// - [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_InfoMessage)] - [ResDescription(StringsHelper.ResourceNames.DbConnection_InfoMessage)] - public event SqlInfoMessageEventHandler InfoMessage - { - add - { - Events.AddHandler(EventInfoMessage, value); - } - remove - { - Events.RemoveHandler(EventInfoMessage, value); - } - } - - /// - public bool FireInfoMessageEventOnUserErrors - { - get => _fireInfoMessageEventOnUserErrors; - set => _fireInfoMessageEventOnUserErrors = value; - } - - // Approx. number of times that the internal connection has been reconnected - internal int ReconnectCount - { - get => _reconnectCount; - } - - internal bool ForceNewConnection { get; set; } - - // - // PUBLIC METHODS - // - /// - new public SqlTransaction BeginTransaction() - { - // this is just a delegate. The actual method tracks executiontime - return BeginTransaction(IsolationLevel.Unspecified, null); - } - - /// - new public SqlTransaction BeginTransaction(IsolationLevel iso) - { - // this is just a delegate. The actual method tracks executiontime - return BeginTransaction(iso, null); - } - - /// - public SqlTransaction BeginTransaction(string transactionName) - { - // Use transaction names only on the outermost pair of nested - // BEGIN...COMMIT or BEGIN...ROLLBACK statements. Transaction names - // are ignored for nested BEGIN's. The only way to rollback a nested - // transaction is to have a save point from a SAVE TRANSACTION call. - return BeginTransaction(IsolationLevel.Unspecified, transactionName); - } - - /// - // suppress this message - we cannot use SafeHandle here. Also, see notes in the code (VSTFDEVDIV# 560355) - [SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")] - override protected DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) - { - using (TryEventScope.Create(" {0}, isolationLevel={1}", ObjectID, (int)isolationLevel)) - { - - DbTransaction transaction = BeginTransaction(isolationLevel); - - // VSTFDEVDIV# 560355 - InnerConnection doesn't maintain a ref on the outer connection (this) and - // subsequently leaves open the possibility that the outer connection could be GC'ed before the SqlTransaction - // is fully hooked up (leaving a DbTransaction with a null connection property). Ensure that this is reachable - // until the completion of BeginTransaction with KeepAlive - GC.KeepAlive(this); - - return transaction; - } - } - - /// - public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionName) - { - WaitForPendingReconnection(); - SqlStatistics statistics = null; - long scopeID = SqlClientEventSource.Log.TryScopeEnterEvent(" {0}, iso={1}, transactionName='{2}'", ObjectID, (int)iso, transactionName); - - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - SqlTransaction transaction; - bool isFirstAttempt = true; - do - { - transaction = GetOpenConnection().BeginSqlTransaction(iso, transactionName, isFirstAttempt); // do not reconnect twice - Debug.Assert(isFirstAttempt || !transaction.InternalTransaction.ConnectionHasBeenRestored, "Restored connection on non-first attempt"); - isFirstAttempt = false; - } while (transaction.InternalTransaction.ConnectionHasBeenRestored); - - - // The GetOpenConnection line above doesn't keep a ref on the outer connection (this), - // and it could be collected before the inner connection can hook it to the transaction, resulting in - // a transaction with a null connection property. Use GC.KeepAlive to ensure this doesn't happen. - GC.KeepAlive(this); - - return transaction; - } - finally - { - SqlClientEventSource.Log.TryScopeLeaveEvent(scopeID); - SqlStatistics.StopTimer(statistics); - } - } - - /// - public override void ChangeDatabase(string database) - { - SqlStatistics statistics = null; - RepairInnerConnection(); - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID{0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - - try - { - statistics = SqlStatistics.StartTimer(Statistics); - InnerConnection.ChangeDatabase(database); - } - // @TODO: CER Exception Handling was removed here (see GH#3581) - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - public static void ClearAllPools() - { - (new SqlClientPermission(PermissionState.Unrestricted)).Demand(); - SqlConnectionFactory.Instance.ClearAllPools(); - } - - /// - public static void ClearPool(SqlConnection connection) - { - ADP.CheckArgumentNull(connection, nameof(connection)); - - DbConnectionOptions connectionOptions = connection.UserConnectionOptions; - if (connectionOptions != null) - { - connectionOptions.DemandPermission(); - SqlConnectionFactory.Instance.ClearPool(connection); - } - } - - private void CloseInnerConnection() - { - // CloseConnection() now handles the lock - - // The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and - // the command will no longer be cancelable. It might be desirable to be able to cancel the close operation, but this is - // outside of the scope of Whidbey RTM. See (SqlCommand::Cancel) for other lock. - _originalConnectionId = ClientConnectionId; - InnerConnection.CloseConnection(this, ConnectionFactory); - } - - /// - public override void Close() - { - using (TryEventScope.Create(" {0}", ObjectID)) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - - ConnectionState previousState = State; - Guid operationId = default(Guid); - Guid clientConnectionId = default(Guid); - - // during the call to Dispose() there is a redundant call to - // Close(). because of this, the second time Close() is invoked the - // connection is already in a closed state. this doesn't seem to be a - // problem except for logging, as we'll get duplicate Before/After/Error - // log entries - if (previousState != ConnectionState.Closed) - { - operationId = s_diagnosticListener.WriteConnectionCloseBefore(this); - // we want to cache the ClientConnectionId for After/Error logging, as when the connection - // is closed then we will lose this identifier - // - // note: caching this is only for diagnostics logging purposes - clientConnectionId = ClientConnectionId; - } - - SqlStatistics statistics = null; - Exception e = null; - - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - Task reconnectTask = _currentReconnectionTask; - if (reconnectTask != null && !reconnectTask.IsCompleted) - { - CancellationTokenSource cts = _reconnectionCancellationSource; - if (cts != null) - { - cts.Cancel(); - } - AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false); // we do not need to deal with possible exceptions in reconnection - if (State != ConnectionState.Open) - {// if we cancelled before the connection was opened - OnStateChange(DbConnectionInternal.StateChangeClosed); - } - } - CancelOpenAndWait(); - CloseInnerConnection(); - GC.SuppressFinalize(this); - - if (Statistics != null) - { - _statistics._closeTimestamp = ADP.TimerCurrent(); - } - } - // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception ex) - { - e = ex; - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - //dispose windows identity once connection is closed. - if (_lastIdentity != null) - { - _lastIdentity.Dispose(); - } - - // we only want to log this if the previous state of the - // connection is open, as that's the valid use-case - if (previousState != ConnectionState.Closed) - { - if (e != null) - { - s_diagnosticListener.WriteConnectionCloseError(operationId, clientConnectionId, this, e); - } - else - { - s_diagnosticListener.WriteConnectionCloseAfter(operationId, clientConnectionId, this); - } - } - } - } - } - - /// - new public SqlCommand CreateCommand() - { - return new SqlCommand(null, this); - } - - private void DisposeMe(bool disposing) - { // MDAC 65459 - // clear credential and AccessToken here rather than in IDisposable.Dispose as these are specific to SqlConnection only - // IDisposable.Dispose is generated code from a template and used by other providers as well - _credential = null; - _accessToken = null; - - if (!disposing) - { - // DevDiv2 Bug 457934:SQLConnection leaks when not disposed - // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/457934 - // 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); - if ((innerConnection != null) && (!innerConnection.ConnectionOptions.Pooling)) - { - var parser = innerConnection.Parser; - if ((parser != null) && (parser._physicalStateObj != null)) - { - parser._physicalStateObj.DecrementPendingCallbacks(release: false); - } - } - } - } - - /// - public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction transaction) => - EnlistDistributedTransactionHelper(transaction); - - /// - public override void Open() => - Open(SqlConnectionOverrides.None); - - private bool TryOpenWithRetry(TaskCompletionSource retry, SqlConnectionOverrides overrides) - => RetryLogicProvider.Execute(this, () => TryOpen(retry, overrides)); - - /// - public void Open(SqlConnectionOverrides overrides) - { - using (TryEventScope.Create(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current)) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - - Guid operationId = s_diagnosticListener.WriteConnectionOpenBefore(this); - - PrepareStatisticsForNewConnection(); - - SqlStatistics statistics = null; - Exception e = null; - - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - if (!(IsProviderRetriable ? TryOpenWithRetry(null, overrides) : TryOpen(null, overrides))) - { - throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending); - } - } - catch (Exception ex) - { - e = ex; - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - - if (e != null) - { - s_diagnosticListener.WriteConnectionOpenError(operationId, this, e); - } - else - { - s_diagnosticListener.WriteConnectionOpenAfter(operationId, this); - } - } - } - } - - internal void RegisterWaitingForReconnect(Task waitingTask) - { - if (((SqlConnectionString)ConnectionOptions).MARS) - { - return; - } - Interlocked.CompareExchange(ref _asyncWaitingForReconnection, waitingTask, null); - if (_asyncWaitingForReconnection != waitingTask) - { // somebody else managed to register - throw SQL.MARSUnsupportedOnConnection(); - } - } - - private async Task ReconnectAsync(int timeout) - { - try - { - long commandTimeoutExpiration = 0; - if (timeout > 0) - { - commandTimeoutExpiration = ADP.TimerCurrent() + ADP.TimerFromSeconds(timeout); - } - CancellationTokenSource cts = new CancellationTokenSource(); - _reconnectionCancellationSource = cts; - CancellationToken ctoken = cts.Token; - int retryCount = _connectRetryCount; // take a snapshot: could be changed by modifying the connection string - for (int attempt = 0; attempt < retryCount; attempt++) - { - if (ctoken.IsCancellationRequested) - { - SqlClientEventSource.Log.TryTraceEvent(" Original ClientConnectionID: {0} - reconnection cancelled.", _originalConnectionId); - return; - } - try - { - _impersonateIdentity = _lastIdentity; - try - { - ForceNewConnection = true; - await OpenAsync(ctoken).ConfigureAwait(false); - // On success, increment the reconnect count - we don't really care if it rolls over since it is approx. - _reconnectCount = unchecked(_reconnectCount + 1); -#if DEBUG - Debug.Assert(_recoverySessionData._debugReconnectDataApplied, "Reconnect data was not applied !"); -#endif - } - finally - { - _impersonateIdentity = null; - ForceNewConnection = false; - } - - SqlClientEventSource.Log.TryTraceEvent(" Reconnection succeeded. ClientConnectionID {0} -> {1}", _originalConnectionId, ClientConnectionId); - return; - } - catch (SqlException e) - { - SqlClientEventSource.Log.TryTraceEvent(" Original ClientConnectionID {0} - reconnection attempt failed error {1}", _originalConnectionId, e.Message); - if (attempt == retryCount - 1) - { - SqlClientEventSource.Log.TryTraceEvent(" Original ClientConnectionID {0} - give up reconnection", _originalConnectionId); - if (e.Class >= TdsEnums.FATAL_ERROR_CLASS) - { - SqlClientEventSource.Log.TryTraceEvent(" Original ClientConnectionID {0} - Fatal Error occured. Error Class: {1}", _originalConnectionId, e.Class); - // Error Class: 20-25, usually terminates the database connection - InnerConnection.CloseConnection(InnerConnection.Owner, ConnectionFactory); - } - throw SQL.CR_AllAttemptsFailed(e, _originalConnectionId); - } - if (timeout > 0 && ADP.TimerRemaining(commandTimeoutExpiration) < ADP.TimerFromSeconds(ConnectRetryInterval)) - { - throw SQL.CR_NextAttemptWillExceedQueryTimeout(e, _originalConnectionId); - } - } - await Task.Delay(1000 * ConnectRetryInterval, ctoken).ConfigureAwait(false); - } - } - finally - { - _recoverySessionData = null; - _suppressStateChangeForReconnection = false; - } - Debug.Fail("Should not reach this point"); - } - - internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) - { - Task runningReconnect = _currentReconnectionTask; - // This loop in the end will return not completed reconnect task or null - while (runningReconnect != null && runningReconnect.IsCompleted) - { - // clean current reconnect task (if it is the same one we checked - Interlocked.CompareExchange(ref _currentReconnectionTask, null, runningReconnect); - // make sure nobody started new task (if which case we did not clean it) - runningReconnect = _currentReconnectionTask; - } - if (runningReconnect == null) - { - if (_connectRetryCount > 0) - { - SqlInternalConnectionTds tdsConn = GetOpenTdsConnection(); - if (tdsConn._sessionRecoveryAcknowledged) - { - TdsParserStateObject stateObj = tdsConn.Parser._physicalStateObj; - if (!stateObj.ValidateSNIConnection()) - { - if (tdsConn.Parser._sessionPool != null) - { - if (tdsConn.Parser._sessionPool.ActiveSessionsCount > 0) - { - // >1 MARS session - if (beforeDisconnect != null) - { - beforeDisconnect(); - } - OnError(SQL.CR_UnrecoverableClient(ClientConnectionId), true, null); - } - } - SessionData cData = tdsConn.CurrentSessionData; - cData.AssertUnrecoverableStateCountIsCorrect(); - if (cData._unrecoverableStatesCount == 0) - { - bool callDisconnect = false; - lock (_reconnectLock) - { - tdsConn.CheckEnlistedTransactionBinding(); - runningReconnect = _currentReconnectionTask; // double check after obtaining the lock - if (runningReconnect == null) - { - if (cData._unrecoverableStatesCount == 0) - { - // could change since the first check, but now is stable since connection is know to be broken - _originalConnectionId = ClientConnectionId; - SqlClientEventSource.Log.TryTraceEvent(" Connection ClientConnectionID {0} is invalid, reconnecting", _originalConnectionId); - _recoverySessionData = cData; - - if (beforeDisconnect != null) - { - beforeDisconnect(); - } - try - { - _suppressStateChangeForReconnection = true; - tdsConn.DoomThisConnection(); - } - catch (SqlException) - { - } - runningReconnect = Task.Run(() => ReconnectAsync(timeout)); - // if current reconnect is not null, somebody already started reconnection task - some kind of race condition - Debug.Assert(_currentReconnectionTask == null, "Duplicate reconnection tasks detected"); - _currentReconnectionTask = runningReconnect; - } - } - else - { - callDisconnect = true; - } - } - if (callDisconnect && beforeDisconnect != null) - { - beforeDisconnect(); - } - } - else - { - if (beforeDisconnect != null) - { - beforeDisconnect(); - } - OnError(SQL.CR_UnrecoverableServer(ClientConnectionId), true, null); - } - } // ValidateSNIConnection - } // sessionRecoverySupported - } // connectRetryCount>0 - } - else - { // runningReconnect = null - if (beforeDisconnect != null) - { - beforeDisconnect(); - } - } - return runningReconnect; - } - - // this is straightforward, but expensive method to do connection resiliency - it take locks and all preparations as for TDS request - partial void RepairInnerConnection() - { - WaitForPendingReconnection(); - if (_connectRetryCount == 0) - { - return; - } - SqlInternalConnectionTds tdsConn = InnerConnection as SqlInternalConnectionTds; - if (tdsConn != null) - { - tdsConn.ValidateConnectionForExecute(null); - tdsConn.GetSessionAndReconnectIfNeeded((SqlConnection)this); - } - } - - private void WaitForPendingReconnection() - { - Task reconnectTask = _currentReconnectionTask; - if (reconnectTask != null && !reconnectTask.IsCompleted) - { - AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false); - } - } - - private void CancelOpenAndWait() - { - // copy from member to avoid changes by background thread - var completion = _currentCompletion; - if (completion != null) - { - completion.Item1.TrySetCanceled(); - ((IAsyncResult)completion.Item2).AsyncWaitHandle.WaitOne(); - } - Debug.Assert(_currentCompletion == null, "After waiting for an async call to complete, there should be no completion source"); - } - - /// - public override Task OpenAsync(CancellationToken cancellationToken) - => OpenAsync(SqlConnectionOverrides.None, cancellationToken); - - /// - public Task OpenAsync(SqlConnectionOverrides overrides, CancellationToken cancellationToken) - => IsProviderRetriable ? - InternalOpenWithRetryAsync(overrides, cancellationToken) : - InternalOpenAsync(overrides, cancellationToken); - - private Task InternalOpenWithRetryAsync(SqlConnectionOverrides overrides, CancellationToken cancellationToken) - => RetryLogicProvider.ExecuteAsync(this, () => InternalOpenAsync(overrides, cancellationToken), cancellationToken); - - private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationToken cancellationToken) - { - long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent(" {0}", ObjectID); - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - - try - { - Guid operationId = s_diagnosticListener.WriteConnectionOpenBefore(this); - - PrepareStatisticsForNewConnection(); - - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - System.Transactions.Transaction transaction = ADP.GetCurrentTransaction(); - TaskCompletionSource completion = new TaskCompletionSource(transaction); - TaskCompletionSource result = new TaskCompletionSource(state: this); - - if (s_diagnosticListener.IsEnabled(SqlClientConnectionOpenAfter.Name) || - s_diagnosticListener.IsEnabled(SqlClientConnectionOpenError.Name)) - { - result.Task.ContinueWith( - continuationAction: s_openAsyncComplete, - state: operationId, // connection is passed in TaskCompletionSource async state - scheduler: TaskScheduler.Default - ); - } - - if (cancellationToken.IsCancellationRequested) - { - result.SetCanceled(); - return result.Task; - } - - bool completed; - - try - { - completed = TryOpen(completion, overrides); - } - catch (Exception e) - { - s_diagnosticListener.WriteConnectionOpenError(operationId, this, e); - result.SetException(e); - return result.Task; - } - - if (completed) - { - result.SetResult(null); - } - else - { - CancellationTokenRegistration registration = new CancellationTokenRegistration(); - if (cancellationToken.CanBeCanceled) - { - registration = cancellationToken.Register(() => completion.TrySetCanceled()); - } - OpenAsyncRetry retry = new OpenAsyncRetry(this, completion, result, overrides, registration); - _currentCompletion = new Tuple, Task>(completion, result.Task); - completion.Task.ContinueWith(retry.Retry, TaskScheduler.Default); - return result.Task; - } - - return result.Task; - } - catch (Exception ex) - { - s_diagnosticListener.WriteConnectionOpenError(operationId, this, ex); - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - finally - { - SqlClientEventSource.Log.TryPoolerScopeLeaveEvent(scopeID); - } - } - - private static void OpenAsyncComplete(Task task, object state) - { - Guid operationId = (Guid)state; - SqlConnection connection = (SqlConnection)task.AsyncState; - if (task.Exception != null) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlConnection.OpenAsyncComplete | Error | Correlation | Activity Id {0}, Exception {1}", ActivityCorrelator.Current, task.Exception.Message); - s_diagnosticListener.WriteConnectionOpenError(operationId, connection, task.Exception); - } - else - { - SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlConnection.OpenAsyncComplete | Info | Correlation | Activity Id {0}, Client Connection Id {1}", ActivityCorrelator.Current, connection?.ClientConnectionId); - s_diagnosticListener.WriteConnectionOpenAfter(operationId, connection); - } - } - - private class OpenAsyncRetry - { - private SqlConnection _parent; - private TaskCompletionSource _retry; - private TaskCompletionSource _result; - private SqlConnectionOverrides _overrides; - private CancellationTokenRegistration _registration; - - public OpenAsyncRetry(SqlConnection parent, TaskCompletionSource retry, TaskCompletionSource result, SqlConnectionOverrides overrides, CancellationTokenRegistration registration) - { - _parent = parent; - _retry = retry; - _result = result; - _overrides = overrides; - _registration = registration; - } - - internal void Retry(Task retryTask) - { - SqlClientEventSource.Log.TryTraceEvent(" {0}", _parent.ObjectID); - _registration.Dispose(); - - try - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(_parent.Statistics); - - if (retryTask.IsFaulted) - { - Exception e = retryTask.Exception.InnerException; - _parent.CloseInnerConnection(); - _parent._currentCompletion = null; - _result.SetException(retryTask.Exception.InnerException); - } - else if (retryTask.IsCanceled) - { - _parent.CloseInnerConnection(); - _parent._currentCompletion = null; - _result.SetCanceled(); - } - else - { - bool result; - // protect continuation from races with close and cancel - lock (_parent.InnerConnection) - { - result = _parent.TryOpen(_retry, _overrides); - } - if (result) - { - _parent._currentCompletion = null; - _result.SetResult(null); - } - else - { - _parent.CloseInnerConnection(); - _parent._currentCompletion = null; - _result.SetException(ADP.ExceptionWithStackTrace(ADP.InternalError(ADP.InternalErrorCode.CompletedConnectReturnedPending))); - } - } - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - catch (Exception e) - { - _parent.CloseInnerConnection(); - _parent._currentCompletion = null; - _result.SetException(e); - } - } - } - - private void PrepareStatisticsForNewConnection() - { - if (StatisticsEnabled || - s_diagnosticListener.IsEnabled(SqlClientCommandAfter.Name) || - s_diagnosticListener.IsEnabled(SqlClientConnectionOpenAfter.Name)) - { - if (_statistics == null) - { - _statistics = new SqlStatistics(); - } - else - { - _statistics.ContinueOnNewConnection(); - } - } - } - - private bool TryOpen(TaskCompletionSource retry, SqlConnectionOverrides overrides = SqlConnectionOverrides.None) - { - SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions; - - bool result = false; - - _applyTransientFaultHandling = (!overrides.HasFlag(SqlConnectionOverrides.OpenWithoutRetry) && connectionOptions != null && connectionOptions.ConnectRetryCount > 0); - - if (connectionOptions != null && - (connectionOptions.Authentication == SqlAuthenticationMethod.SqlPassword || - connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || - connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) && - (!connectionOptions._hasUserIdKeyword || !connectionOptions._hasPasswordKeyword) && - _credential == null) - { - throw SQL.CredentialsNotProvided(connectionOptions.Authentication); - } - - if (_impersonateIdentity != null) - { - using (WindowsIdentity identity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity()) - { - if (_impersonateIdentity.User == identity.User) - { - result = TryOpenInner(retry); - } - else - { - using (WindowsImpersonationContext context = _impersonateIdentity.Impersonate()) - { - result = TryOpenInner(retry); - } - } - } - } - else - { - if (this.UsesIntegratedSecurity(connectionOptions) || this.UsesActiveDirectoryIntegrated(connectionOptions)) - { - _lastIdentity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity(); - } - else - { - _lastIdentity = null; - } - result = TryOpenInner(retry); - } - - return result; - } - - private bool TryOpenInner(TaskCompletionSource retry) - { - if (ForceNewConnection) - { - if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry, UserConnectionOptions)) - { - return false; - } - } - else - { - if (!InnerConnection.TryOpenConnection(this, ConnectionFactory, retry, UserConnectionOptions)) - { - return false; - } - } - // does not require GC.KeepAlive(this) because of OnStateChange - - var tdsInnerConnection = (SqlInternalConnectionTds)InnerConnection; - Debug.Assert(tdsInnerConnection.Parser != null, "Where's the parser?"); - - if (!tdsInnerConnection.ConnectionOptions.Pooling) - { - // For non-pooled connections, we need to make sure that the finalizer does actually run to avoid leaking SNI handles - GC.ReRegisterForFinalize(this); - } - - // The _statistics can change with StatisticsEnabled. Copying to a local variable before checking for a null value. - SqlStatistics statistics = _statistics; - if (StatisticsEnabled || - (s_diagnosticListener.IsEnabled(SqlClientCommandAfter.Name) && statistics != null)) - { - _statistics._openTimestamp = ADP.TimerCurrent(); - tdsInnerConnection.Parser.Statistics = _statistics; - } - else - { - tdsInnerConnection.Parser.Statistics = null; - _statistics = null; // in case of previous Open/Close/reset_CollectStats sequence - } - // @TODO: CER Exception Handling was removed here (see GH#3581) - - return true; - } - - - // - // INTERNAL PROPERTIES - // - - internal bool HasLocalTransaction - { - get - { - return GetOpenConnection().HasLocalTransaction; - } - } - - internal bool HasLocalTransactionFromAPI - { - get - { - Task reconnectTask = _currentReconnectionTask; - if (reconnectTask != null && !reconnectTask.IsCompleted) - { - return false; //we will not go into reconnection if we are inside the transaction - } - return GetOpenConnection().HasLocalTransactionFromAPI; - } - } - - internal bool Is2008OrNewer - { - get - { - if (_currentReconnectionTask != null) - { // holds true even if task is completed - return true; // if CR is enabled, connection, if established, will be 2008+ - } - return GetOpenConnection().Is2008OrNewer; - } - } - - internal TdsParser Parser - { - get - { - SqlInternalConnectionTds tdsConnection = GetOpenTdsConnection(); - return tdsConnection.Parser; - } - } - - // - // INTERNAL METHODS - // - - internal void ValidateConnectionForExecute(string method, SqlCommand command) - { - Task asyncWaitingForReconnection = _asyncWaitingForReconnection; - if (asyncWaitingForReconnection != null) - { - if (!asyncWaitingForReconnection.IsCompleted) - { - throw SQL.MARSUnsupportedOnConnection(); - } - else - { - Interlocked.CompareExchange(ref _asyncWaitingForReconnection, null, asyncWaitingForReconnection); - } - } - if (_currentReconnectionTask != null) - { - Task currentReconnectionTask = _currentReconnectionTask; - if (currentReconnectionTask != null && !currentReconnectionTask.IsCompleted) - { - return; // execution will wait for this task later - } - } - SqlInternalConnection innerConnection = GetOpenConnection(method); - innerConnection.ValidateConnectionForExecute(command); - } - - // Surround name in brackets and then escape any end bracket to protect against SQL Injection. - // NOTE: if the user escapes it themselves it will not work, but this was the case in V1 as well - // as native OleDb and Odbc. - internal static string FixupDatabaseTransactionName(string name) - { - if (!string.IsNullOrEmpty(name)) - { - return SqlServerEscapeHelper.EscapeIdentifier(name); - } - else - { - return name; - } - } - - // 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) - { - Debug.Assert(exception != null && exception.Errors.Count != 0, "SqlConnection: OnError called with null or empty exception!"); - - if (breakConnection && (ConnectionState.Open == State)) - { - if (wrapCloseInAction != null) - { - int capturedCloseCount = _closeCount; - - Action closeAction = () => - { - if (capturedCloseCount == _closeCount) - { - SqlClientEventSource.Log.TryTraceEvent(" {0}, Connection broken.", ObjectID); - Close(); - } - }; - - wrapCloseInAction(closeAction); - } - else - { - SqlClientEventSource.Log.TryTraceEvent(" {0}, Connection broken.", ObjectID); - Close(); - } - } - - 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; - } - else - { - // If it is a class < TdsEnums.MIN_ERROR_CLASS, it is a warning collection - so pass to handler - this.OnInfoMessage(new SqlInfoMessageEventArgs(exception)); - } - } - - // - // PRIVATE METHODS - // - - internal SqlInternalConnection GetOpenConnection() - { - SqlInternalConnection innerConnection = (InnerConnection as SqlInternalConnection); - if (innerConnection == null) - { - throw ADP.ClosedConnectionError(); - } - return innerConnection; - } - - internal SqlInternalConnection GetOpenConnection(string method) - { - DbConnectionInternal innerConnection = InnerConnection; - SqlInternalConnection innerSqlConnection = (innerConnection as SqlInternalConnection); - if (innerSqlConnection == null) - { - throw ADP.OpenConnectionRequired(method, innerConnection.State); - } - return innerSqlConnection; - } - - internal SqlInternalConnectionTds GetOpenTdsConnection() - { - SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); - if (innerConnection == null) - { - throw ADP.ClosedConnectionError(); - } - return innerConnection; - } - - internal SqlInternalConnectionTds GetOpenTdsConnection(string method) - { - SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); - if (innerConnection == null) - { - throw ADP.OpenConnectionRequired(method, InnerConnection.State); - } - return innerConnection; - } - - internal void OnInfoMessage(SqlInfoMessageEventArgs imevent) - { - OnInfoMessage(imevent, out _); - } - - internal void OnInfoMessage(SqlInfoMessageEventArgs imevent, out bool notified) - { - - Debug.Assert(imevent != null, "null SqlInfoMessageEventArgs"); - var imeventValue = imevent != null ? imevent.Message : ""; - SqlClientEventSource.Log.TryTraceEvent(" {0}, Message='{1}'", ObjectID, imeventValue); - SqlInfoMessageEventHandler handler = (SqlInfoMessageEventHandler)Events[EventInfoMessage]; - - if (handler != null) - { - notified = true; - try - { - handler(this, imevent); - } - catch (Exception e) - { // MDAC 53175 - if (!ADP.IsCatchableOrSecurityExceptionType(e)) - { - throw; - } - - ADP.TraceExceptionWithoutRethrow(e); - } - } - else - { - notified = false; - } - } - - /// - public static void ChangePassword(string connectionString, string newPassword) - { - using (TryEventScope.Create(nameof(SqlConnection))) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ActivityID {0}", ActivityCorrelator.Current); - - if (string.IsNullOrEmpty(connectionString)) - { - throw SQL.ChangePasswordArgumentMissing("connectionString"); - } - if (string.IsNullOrEmpty(newPassword)) - { - throw SQL.ChangePasswordArgumentMissing(nameof(newPassword)); - } - if (TdsEnums.MAXLEN_NEWPASSWORD < newPassword.Length) - { - throw ADP.InvalidArgumentLength(nameof(newPassword), TdsEnums.MAXLEN_NEWPASSWORD); - } - - SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null); - - SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key); - if (connectionOptions.IntegratedSecurity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) - { - throw SQL.ChangePasswordConflictsWithSSPI(); - } - if (!string.IsNullOrEmpty(connectionOptions.AttachDBFilename)) - { - throw SQL.ChangePasswordUseOfUnallowedKey(DbConnectionStringKeywords.AttachDbFilename); - } - - PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); - permissionSet.Demand(); - - ChangePassword(connectionString, connectionOptions, null, newPassword, null); - } - } - - /// - public static void ChangePassword(string connectionString, SqlCredential credential, SecureString newSecurePassword) - { - using (TryEventScope.Create(nameof(SqlConnection))) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ActivityID {0}", ActivityCorrelator.Current); - - if (string.IsNullOrEmpty(connectionString)) - { - throw SQL.ChangePasswordArgumentMissing(nameof(connectionString)); - } - - // check credential; not necessary to check the length of password in credential as the check is done by SqlCredential class - if (credential == null) - { - throw SQL.ChangePasswordArgumentMissing(nameof(credential)); - } - - if (newSecurePassword == null || newSecurePassword.Length == 0) - { - throw SQL.ChangePasswordArgumentMissing(nameof(newSecurePassword)); - } - - if (!newSecurePassword.IsReadOnly()) - { - throw ADP.MustBeReadOnly(nameof(newSecurePassword)); - } - - if (TdsEnums.MAXLEN_NEWPASSWORD < newSecurePassword.Length) - { - throw ADP.InvalidArgumentLength(nameof(newSecurePassword), TdsEnums.MAXLEN_NEWPASSWORD); - } - - SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null, sspiContextProvider: null); - - SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key); - - // Check for connection string values incompatible with SqlCredential - if (!string.IsNullOrEmpty(connectionOptions.UserID) || !string.IsNullOrEmpty(connectionOptions.Password)) - { - throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); - } - - if (connectionOptions.IntegratedSecurity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) - { - throw SQL.ChangePasswordConflictsWithSSPI(); - } - - if (!string.IsNullOrEmpty(connectionOptions.AttachDBFilename)) - { - throw SQL.ChangePasswordUseOfUnallowedKey(DbConnectionStringKeywords.AttachDbFilename); - } - - PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); - permissionSet.Demand(); - - ChangePassword(connectionString, connectionOptions, credential, null, newSecurePassword); - } - } - - private static void ChangePassword(string connectionString, SqlConnectionString connectionOptions, SqlCredential credential, string newPassword, SecureString newSecurePassword) - { - // 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; - try - { - con = new SqlInternalConnectionTds(null, connectionOptions, credential, null, newPassword, newSecurePassword, false); - } - finally - { - con?.Dispose(); - } - SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null, sspiContextProvider: null); - - SqlConnectionFactory.Instance.ClearPool(key); - } - - internal Task RegisterForConnectionCloseNotification(Task outerTask, object value, int tag) - { - // Connection exists, schedule removal, will be added to ref collection after calling ValidateAndReconnect - - object state = null; - if (outerTask.AsyncState == this) - { - // if the caller created the TaskCompletionSource for outerTask with this connection - // as the state parameter (which is immutable) we can use task.AsyncState and state - // to carry the two pieces of state that we need into the continuation avoiding the - // allocation of a new state object to carry them - state = value; - } - else - { - // otherwise we need to create a Tuple to carry the two pieces of state - state = Tuple.Create(this, value); - } - - return outerTask.ContinueWith( - continuationFunction: static (task, state) => - { - SqlConnection connection = null; - object obj = null; - if (state is Tuple tuple) - { - // special state tuple, unpack it - connection = tuple.Item1; - obj = tuple.Item2; - } - else - { - // use state on task and state object - connection = (SqlConnection)task.AsyncState; - obj = state; - } - - connection.RemoveWeakReference(obj); - return task; - }, - state: state, - scheduler: TaskScheduler.Default - ).Unwrap(); - } - - /// - public void ResetStatistics() - { - if (Statistics != null) - { - Statistics.Reset(); - if (ConnectionState.Open == State) - { - // update timestamp; - _statistics._openTimestamp = ADP.TimerCurrent(); - } - } - } - - /// - public IDictionary RetrieveStatistics() - { - if (Statistics != null) - { - UpdateStatistics(); - return Statistics.GetDictionary(); - } - else - { - return new SqlStatistics().GetDictionary(); - } - } - - private void UpdateStatistics() - { - if (ConnectionState.Open == State) - { - // update timestamp - _statistics._closeTimestamp = ADP.TimerCurrent(); - } - // delegate the rest of the work to the SqlStatistics class - Statistics.UpdateStatistics(); - } - - /// - public IDictionary RetrieveInternalInfo() - { - IDictionary internalDictionary = new Dictionary(); - - internalDictionary.Add("SQLDNSCachingSupportedState", SQLDNSCachingSupportedState); - internalDictionary.Add("SQLDNSCachingSupportedStateBeforeRedirect", SQLDNSCachingSupportedStateBeforeRedirect); - - return internalDictionary; - } - - /// - object ICloneable.Clone() => new SqlConnection(this); - - // UDT SUPPORT - private Assembly ResolveTypeAssembly(AssemblyName asmRef, bool throwOnError) - { - Debug.Assert(TypeSystemAssemblyVersion != null, "TypeSystemAssembly should be set !"); - if (string.Equals(asmRef.Name, "Microsoft.SqlServer.Types", StringComparison.OrdinalIgnoreCase)) - { - if (asmRef.Version != TypeSystemAssemblyVersion && SqlClientEventSource.Log.IsTraceEnabled()) - { - SqlClientEventSource.Log.TryTraceEvent(" SQL CLR type version change: Server sent {0}, client will instantiate {1}", asmRef.Version, TypeSystemAssemblyVersion); - } - asmRef.Version = TypeSystemAssemblyVersion; - } - - try - { - return Assembly.Load(asmRef); - } - catch (Exception e) - { - if (throwOnError || !ADP.IsCatchableExceptionType(e)) - { - throw; - } - else - { - return null; - } - } - } - - internal void CheckGetExtendedUDTInfo(SqlMetaDataPriv metaData, bool fThrow) - { - if (metaData.udt?.Type == null) - { // If null, we have not obtained extended info. - Debug.Assert(!string.IsNullOrEmpty(metaData.udt?.AssemblyQualifiedName), "Unexpected state on GetUDTInfo"); - // Parameter throwOnError determines whether exception from Assembly.Load is thrown. - metaData.udt.Type = - Type.GetType(typeName: metaData.udt.AssemblyQualifiedName, assemblyResolver: asmRef => ResolveTypeAssembly(asmRef, fThrow), typeResolver: null, throwOnError: fThrow); - - if (fThrow && metaData.udt.Type == null) - { - throw SQL.UDTUnexpectedResult(metaData.udt.AssemblyQualifiedName); - } - } - } - - internal object GetUdtValue(object value, SqlMetaDataPriv metaData, bool returnDBNull) - { - if (returnDBNull && ADP.IsNull(value)) - { - return DBNull.Value; - } - - object o = null; - - // Since the serializer doesn't handle nulls... - if (ADP.IsNull(value)) - { - Type t = metaData.udt?.Type; - Debug.Assert(t != null, "Unexpected null of udtType on GetUdtValue!"); - o = t.InvokeMember("Null", BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static, null, null, Array.Empty(), CultureInfo.InvariantCulture); - Debug.Assert(o != null); - return o; - } - else - { - - MemoryStream stm = new MemoryStream((byte[])value); - - o = Server.SerializationHelperSql9.Deserialize(stm, metaData.udt?.Type); - - Debug.Assert(o != null, "object could NOT be created"); - return o; - } - } - - internal byte[] GetBytes(object o, out int maxSize) - { - SqlUdtInfo attr = GetInfoFromType(o.GetType()); - maxSize = attr.MaxByteSize; - - if (maxSize < -1 || maxSize >= ushort.MaxValue) - { - throw new InvalidOperationException(o.GetType() + ": invalid Size"); - } - - byte[] retval; - - using (MemoryStream stm = new MemoryStream(maxSize < 0 ? 0 : maxSize)) - { - Server.SerializationHelperSql9.Serialize(stm, o); - retval = stm.ToArray(); - } - return retval; - } - - private SqlUdtInfo GetInfoFromType(Type t) - { - Debug.Assert(t != null, "Type object can't be NULL"); - Type orig = t; - do - { - SqlUdtInfo attr = SqlUdtInfo.TryGetFromType(t); - if (attr != null) - { - return attr; - } - - t = t.BaseType; - } - while (t != null); - - throw SQL.UDTInvalidSqlType(orig.AssemblyQualifiedName); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs index efd2062c4d..20c0de6e90 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs @@ -36,26 +36,6 @@ public SqlConnection() : base() _innerConnection = DbConnectionClosedNeverOpened.SingletonInstance; } - // Copy Constructor - private void CopyFrom(SqlConnection connection) - { // V1.2.3300 - ADP.CheckArgumentNull(connection, "connection"); - _userConnectionOptions = connection.UserConnectionOptions; - _poolGroup = connection.PoolGroup; - - // SQLBU 432115 - // Match the original connection's behavior for whether the connection was never opened, - // but ensure Clone is in the closed state. - if (DbConnectionClosedNeverOpened.SingletonInstance == connection._innerConnection) - { - _innerConnection = DbConnectionClosedNeverOpened.SingletonInstance; - } - else - { - _innerConnection = DbConnectionClosedPreviouslyOpened.SingletonInstance; - } - } - /// We use the _closeCount to avoid having to know about all our /// children; instead of keeping a collection of all the objects that /// would be affected by a close, we simply increment the _closeCount @@ -311,27 +291,6 @@ internal DbMetaDataFactory GetMetaDataFactoryInternal(DbConnectionInternal inter return GetMetaDataFactory(internalConnection); } - /// - override public DataTable GetSchema() - { - return this.GetSchema(DbMetaDataCollectionNames.MetaDataCollections, null); - } - - /// - override public DataTable GetSchema(string collectionName) - { - return this.GetSchema(collectionName, null); - } - - /// - override public DataTable GetSchema(string collectionName, string[] restrictionValues) - { - // NOTE: This is virtual because not all providers may choose to support - // returning schema data - SqlConnection.ExecutePermission.Demand(); - return InnerConnection.GetSchema(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues); - } - internal void NotifyWeakReference(int message) { InnerConnection.NotifyWeakReference(message); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs similarity index 87% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 0fbef8b6db..d8e742e3d5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -23,14 +23,28 @@ using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Data.SqlClient.Diagnostics; using Microsoft.SqlServer.Server; +#if NETFRAMEWORK +using System.Runtime.CompilerServices; +using System.Security.Permissions; +using System.Security.Principal; +#endif +#if NETFRAMEWORK +[assembly: InternalsVisibleTo("System.Data.DataSetExtensions, PublicKey=" + Microsoft.Data.SqlClient.AssemblyRef.EcmaPublicKeyFull)] // DevDiv Bugs 92166 +// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available. +// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future. +#endif namespace Microsoft.Data.SqlClient { - /// + /// [DefaultEvent("InfoMessage")] [DesignerCategory("")] public sealed partial class SqlConnection : DbConnection, ICloneable { +#if NETFRAMEWORK + private static readonly object EventInfoMessage = new object(); +#endif + private bool _AsyncCommandInProgress; // SQLStatistics support @@ -55,6 +69,10 @@ public sealed partial class SqlConnection : DbConnection, ICloneable private CancellationTokenSource _reconnectionCancellationSource; internal SessionData _recoverySessionData; internal bool _suppressStateChangeForReconnection; +#if NETFRAMEWORK + internal WindowsIdentity _lastIdentity; + internal WindowsIdentity _impersonateIdentity; +#endif private int _reconnectCount; // Retry Logic @@ -81,6 +99,7 @@ private static readonly Dictionary private IReadOnlyDictionary _customColumnEncryptionKeyStoreProviders; private Func> _accessTokenCallback; + private SspiContextProvider _sspiContextProvider; internal bool HasColumnEncryptionKeyStoreProvidersRegistered => @@ -110,7 +129,7 @@ private static readonly ConcurrentDictionary> _ColumnEncry private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider); - /// + /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public SqlRetryLogicBaseProvider RetryLogicProvider @@ -129,31 +148,31 @@ public SqlRetryLogicBaseProvider RetryLogicProvider } } - /// + /// [DefaultValue(null)] [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_ColumnEncryptionKeyCacheTtl)] public static TimeSpan ColumnEncryptionKeyCacheTtl { get; set; } = TimeSpan.FromHours(2); - /// + /// [DefaultValue(null)] [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled)] public static bool ColumnEncryptionQueryMetadataCacheEnabled { get; set; } = true; - /// + /// [DefaultValue(null)] [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_TrustedColumnMasterKeyPaths)] public static IDictionary> ColumnEncryptionTrustedMasterKeyPaths => _ColumnEncryptionTrustedMasterKeyPaths; - /// + /// public SqlConnection(string connectionString) : this() { - ConnectionString = connectionString; // setting connection string first so that ConnectionOption is available + ConnectionString = connectionString; } - /// + /// public SqlConnection(string connectionString, SqlCredential credential) : this() { ConnectionString = connectionString; // setting connection string first so that ConnectionOption is available @@ -303,7 +322,7 @@ internal bool IsColumnEncryptionSettingEnabled } } - /// + /// public static void RegisterColumnEncryptionKeyStoreProviders(IDictionary customProviders) { ValidateCustomProviders(customProviders); @@ -335,7 +354,7 @@ public static void RegisterColumnEncryptionKeyStoreProviders(IDictionary + /// public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(IDictionary customProviders) { ValidateCustomProviders(customProviders); @@ -434,7 +453,7 @@ private void CacheConnectionStringProperties() // PUBLIC PROPERTIES // - /// + /// // used to start/stop collection of statistics data and do verify the current state // // devnote: start/stop should not performed using a property since it requires execution of code @@ -455,41 +474,39 @@ public bool StatisticsEnabled } set { + if (value) { - if (value) + // start + if (ConnectionState.Open == State) { - // start - if (ConnectionState.Open == State) + if (_statistics == null) { - if (_statistics == null) - { - _statistics = new SqlStatistics(); - _statistics._openTimestamp = ADP.TimerCurrent(); - } - // set statistics on the parser - // update timestamp; - Debug.Assert(Parser != null, "Where's the parser?"); - Parser.Statistics = _statistics; + _statistics = new SqlStatistics(); + _statistics._openTimestamp = ADP.TimerCurrent(); } + // set statistics on the parser + // update timestamp; + Debug.Assert(Parser != null, "Where's the parser?"); + Parser.Statistics = _statistics; } - else + } + else + { + // stop + if (_statistics != null) { - // stop - if (_statistics != null) + if (ConnectionState.Open == State) { - if (ConnectionState.Open == State) - { - // remove statistics from parser - // update timestamp; - TdsParser parser = Parser; - Debug.Assert(parser != null, "Where's the parser?"); - parser.Statistics = null; - _statistics._closeTimestamp = ADP.TimerCurrent(); - } + // remove statistics from parser + // update timestamp; + TdsParser parser = Parser; + Debug.Assert(parser != null, "Where's the parser?"); + parser.Statistics = null; + _statistics._closeTimestamp = ADP.TimerCurrent(); } } - _collectstats = value; } + _collectstats = value; } } @@ -576,8 +593,11 @@ internal int ConnectRetryInterval get => ((SqlConnectionString)ConnectionOptions).ConnectRetryInterval; } - /// + /// [DefaultValue("")] +#pragma warning disable 618 // ignore obsolete warning about RecommendedAsConfigurable to use SettingsBindableAttribute + [RecommendedAsConfigurable(true)] +#pragma warning restore 618 [SettingsBindableAttribute(true)] [RefreshProperties(RefreshProperties.All)] [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] @@ -647,7 +667,7 @@ public override string ConnectionString } } - /// + /// [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout)] [ResCategory(StringsHelper.ResourceNames.SqlConnection_DataSource)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] @@ -660,7 +680,7 @@ public override int ConnectionTimeout } } - /// + /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout)] public int CommandTimeout @@ -672,7 +692,7 @@ public int CommandTimeout } } - /// + /// // AccessToken: To be used for token based authentication [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] @@ -706,7 +726,7 @@ public string AccessToken } } - /// + /// public Func> AccessTokenCallback { get { return _accessTokenCallback; } @@ -739,7 +759,7 @@ internal SspiContextProvider SspiContextProvider } } - /// + /// [ResDescription(StringsHelper.ResourceNames.SqlConnection_Database)] [ResCategory(StringsHelper.ResourceNames.SqlConnection_DataSource)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] @@ -814,7 +834,7 @@ internal string SQLDNSCachingSupportedStateBeforeRedirect } } - /// + /// [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [ResDescription(StringsHelper.ResourceNames.SqlConnection_DataSource)] @@ -839,7 +859,7 @@ public override string DataSource } } - /// + /// [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] [ResDescription(StringsHelper.ResourceNames.SqlConnection_PacketSize)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] @@ -850,10 +870,9 @@ public int PacketSize // can just return what the connection string had. get { - SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); int result; - if (innerConnection != null) + if (InnerConnection is SqlInternalConnectionTds innerConnection) { result = innerConnection.PacketSize; } @@ -862,11 +881,12 @@ public int PacketSize SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; result = constr != null ? constr.PacketSize : DbConnectionStringDefaults.PacketSize; } + return result; } } - /// + /// [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] [ResDescription(StringsHelper.ResourceNames.SqlConnection_ClientConnectionId)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] @@ -894,7 +914,7 @@ public Guid ClientConnectionId } } - /// + /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [ResDescription(StringsHelper.ResourceNames.SqlConnection_ServerVersion)] @@ -903,7 +923,7 @@ public override string ServerVersion get => GetOpenTdsConnection().ServerVersion; } - /// + /// [Browsable(false)] [ResDescription(StringsHelper.ResourceNames.SqlConnection_ServerProcessId)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] @@ -911,7 +931,7 @@ public int ServerProcessId { get { - if ((State & (ConnectionState.Open | ConnectionState.Executing | ConnectionState.Fetching)) > 0) + if ((State & (ConnectionState.Open | ConnectionState.Executing | ConnectionState.Fetching)) != 0) { return GetOpenTdsConnection().ServerProcessId; } @@ -919,7 +939,7 @@ public int ServerProcessId } } - /// + /// [Browsable(false)] [ResDescription(StringsHelper.ResourceNames.DbConnection_State)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] @@ -942,7 +962,7 @@ internal SqlStatistics Statistics get => _statistics; } - /// + /// [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] [ResDescription(StringsHelper.ResourceNames.SqlConnection_WorkstationId)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] @@ -954,12 +974,15 @@ public string WorkstationId // Note: In Longhorn you'll be able to rename a machine without // rebooting. Therefore, don't cache this machine name. SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - string result = constr?.WorkstationId ?? Environment.MachineName; - return result; + + return constr?.WorkstationId + // Getting machine name requires Environment.Permission. + // User must have that permission in order to retrieve this + ?? Environment.MachineName; } } - /// + /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [ResDescription(StringsHelper.ResourceNames.SqlConnection_Credential)] @@ -1081,7 +1104,11 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(S // Check if the usage of AccessToken has the conflict with credential if (_credential != null) { +#if NET throw ADP.InvalidMixedUsageOfCredentialAndAccessToken(); +#else + throw ADP.InvalidMixedUsageOfAccessTokenAndCredential(); +#endif } if (_accessTokenCallback != null) @@ -1112,7 +1139,7 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCa } } - /// + /// protected override DbProviderFactory DbProviderFactory { get => SqlClientFactory.Instance; @@ -1122,12 +1149,26 @@ protected override DbProviderFactory DbProviderFactory // PUBLIC EVENTS // - /// + /// [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_InfoMessage)] [ResDescription(StringsHelper.ResourceNames.DbConnection_InfoMessage)] +#if NET public event SqlInfoMessageEventHandler InfoMessage; +#else + public event SqlInfoMessageEventHandler InfoMessage + { + add + { + Events.AddHandler(EventInfoMessage, value); + } + remove + { + Events.RemoveHandler(EventInfoMessage, value); + } + } +#endif - /// + /// public bool FireInfoMessageEventOnUserErrors { get => _fireInfoMessageEventOnUserErrors; @@ -1142,7 +1183,8 @@ internal int ReconnectCount internal bool ForceNewConnection { get; set; } - /// +#if NET + /// protected override void OnStateChange(StateChangeEventArgs stateChange) { if (!_suppressStateChangeForReconnection) @@ -1150,25 +1192,26 @@ protected override void OnStateChange(StateChangeEventArgs stateChange) base.OnStateChange(stateChange); } } +#endif // // PUBLIC METHODS // - /// + /// new public SqlTransaction BeginTransaction() { // this is just a delegate. The actual method tracks executiontime return BeginTransaction(IsolationLevel.Unspecified, null); } - /// + /// new public SqlTransaction BeginTransaction(IsolationLevel iso) { // this is just a delegate. The actual method tracks executiontime return BeginTransaction(iso, null); } - /// + /// public SqlTransaction BeginTransaction(string transactionName) { // Use transaction names only on the outermost pair of nested @@ -1178,7 +1221,7 @@ public SqlTransaction BeginTransaction(string transactionName) return BeginTransaction(IsolationLevel.Unspecified, transactionName); } - /// + /// [SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")] override protected DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) { @@ -1196,7 +1239,7 @@ override protected DbTransaction BeginDbTransaction(IsolationLevel isolationLeve } } - /// + /// public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionName) { WaitForPendingReconnection(); @@ -1231,7 +1274,7 @@ public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionNam } } - /// + /// public override void ChangeDatabase(string database) { SqlStatistics statistics = null; @@ -1250,13 +1293,16 @@ public override void ChangeDatabase(string database) } } - /// + /// public static void ClearAllPools() { +#if NETFRAMEWORK + (new SqlClientPermission(PermissionState.Unrestricted)).Demand(); +#endif SqlConnectionFactory.Instance.ClearAllPools(); } - /// + /// public static void ClearPool(SqlConnection connection) { ADP.CheckArgumentNull(connection, nameof(connection)); @@ -1264,6 +1310,9 @@ public static void ClearPool(SqlConnection connection) DbConnectionOptions connectionOptions = connection.UserConnectionOptions; if (connectionOptions != null) { +#if NETFRAMEWORK + connectionOptions.DemandPermission(); +#endif SqlConnectionFactory.Instance.ClearPool(connection); } } @@ -1279,7 +1328,7 @@ private void CloseInnerConnection() InnerConnection.CloseConnection(this, ConnectionFactory); } - /// + /// public override void Close() { using (TryEventScope.Create("SqlConnection.Close | API | Object Id {0}", ObjectID)) @@ -1344,6 +1393,13 @@ public override void Close() finally { SqlStatistics.StopTimer(statistics); +#if NETFRAMEWORK + // Dispose windows identity once connection is closed. + if (_lastIdentity != null) + { + _lastIdentity.Dispose(); + } +#endif // we only want to log this if the previous state of the // connection is open, as that's the valid use-case @@ -1362,7 +1418,7 @@ public override void Close() } } - /// + /// new public SqlCommand CreateCommand() { return new SqlCommand(null, this); @@ -1390,16 +1446,20 @@ private void DisposeMe(bool disposing) } } - /// - public override void Open() - { +#if NETFRAMEWORK + /// + public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction transaction) => + EnlistDistributedTransactionHelper(transaction); +#endif + + /// + public override void Open() => Open(SqlConnectionOverrides.None); - } private bool TryOpenWithRetry(TaskCompletionSource retry, SqlConnectionOverrides overrides) => RetryLogicProvider.Execute(this, () => TryOpen(retry, overrides)); - /// + /// public void Open(SqlConnectionOverrides overrides) { using (TryEventScope.Create("SqlConnection.Open | API | Correlation | Object Id {0}, Activity Id {1}", ObjectID, ActivityCorrelator.Current)) @@ -1478,6 +1538,9 @@ private async Task ReconnectAsync(int timeout) } try { +#if NETFRAMEWORK + _impersonateIdentity = _lastIdentity; +#endif try { ForceNewConnection = true; @@ -1490,8 +1553,12 @@ private async Task ReconnectAsync(int timeout) } finally { +#if NETFRAMEWORK + _impersonateIdentity = null; +#endif ForceNewConnection = false; } + SqlClientEventSource.Log.TryTraceEvent("SqlConnection.ReconnectAsync | Info | Reconnection succeeded. Client Connection Id {0} -> {1}", _originalConnectionId, ClientConnectionId); return; } @@ -1670,11 +1737,11 @@ private void CancelOpenAndWait() Debug.Assert(_currentCompletion == null, "After waiting for an async call to complete, there should be no completion source"); } - /// + /// public override Task OpenAsync(CancellationToken cancellationToken) => OpenAsync(SqlConnectionOverrides.None, cancellationToken); - /// + /// public Task OpenAsync(SqlConnectionOverrides overrides, CancellationToken cancellationToken) => IsProviderRetriable ? InternalOpenWithRetryAsync(overrides, cancellationToken) : @@ -1687,6 +1754,7 @@ private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationTok { long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent("SqlConnection.InternalOpenAsync | API | Object Id {0}", ObjectID); SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlConnection.InternalOpenAsync | API | Correlation | Object Id {0}, Activity Id {1}", ObjectID, ActivityCorrelator.Current); + try { Guid operationId = s_diagnosticListener.WriteConnectionOpenBefore(this); @@ -1787,30 +1855,35 @@ private static void OpenAsyncCancel(object state) ((TaskCompletionSource)state).TrySetCanceled(); } - /// + /// public override DataTable GetSchema() { return GetSchema(DbMetaDataCollectionNames.MetaDataCollections, null); } - /// + /// public override DataTable GetSchema(string collectionName) { return GetSchema(collectionName, null); } - /// + /// public override DataTable GetSchema(string collectionName, string[] restrictionValues) { SqlClientEventSource.Log.TryTraceEvent("SqlConnection.GetSchema | Info | Object Id {0}, Collection Name '{1}'", ObjectID, collectionName); +#if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); +#endif return InnerConnection.GetSchema(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues); } - /// +#if NET + /// public override bool CanCreateBatch => true; - /// + /// protected override DbBatch CreateDbBatch() => new SqlBatch(this); +#endif private class OpenAsyncRetry { @@ -1910,6 +1983,7 @@ private void PrepareStatisticsForNewConnection() private bool TryOpen(TaskCompletionSource retry, SqlConnectionOverrides overrides = SqlConnectionOverrides.None) { SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions; + bool result = false; if (LocalAppContextSwitches.GlobalizationInvariantMode) { @@ -1928,6 +2002,45 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec throw SQL.CredentialsNotProvided(connectionOptions.Authentication); } +#if NETFRAMEWORK + if (_impersonateIdentity != null) + { + using (WindowsIdentity identity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity()) + { + if (_impersonateIdentity.User == identity.User) + { + result = TryOpenInner(retry); + } + else + { + using (WindowsImpersonationContext context = _impersonateIdentity.Impersonate()) + { + result = TryOpenInner(retry); + } + } + } + } + else + { + if (this.UsesIntegratedSecurity(connectionOptions) || this.UsesActiveDirectoryIntegrated(connectionOptions)) + { + _lastIdentity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity(); + } + else + { + _lastIdentity = null; + } + result = TryOpenInner(retry); + } +#else + result = TryOpenInner(retry); +#endif + + return result; + } + + private bool TryOpenInner(TaskCompletionSource retry) + { if (ForceNewConnection) { if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry, UserConnectionOptions)) @@ -1967,6 +2080,7 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec tdsInnerConnection.Parser.Statistics = null; _statistics = null; // in case of previous Open/Close/reset_CollectStats sequence } + // @TODO: CER Exception Handling was removed here (see GH#3581) return true; } @@ -2110,7 +2224,6 @@ internal void OnError(SqlException exception, bool breakConnection, Action + /// public static void ChangePassword(string connectionString, string newPassword) { using (TryEventScope.Create("SqlConnection.ChangePassword | API | Password change requested.")) @@ -2170,7 +2291,7 @@ public static void ChangePassword(string connectionString, string newPassword) if (string.IsNullOrEmpty(connectionString)) { - throw SQL.ChangePasswordArgumentMissing(nameof(newPassword)); + throw SQL.ChangePasswordArgumentMissing(nameof(connectionString)); } if (string.IsNullOrEmpty(newPassword)) { @@ -2184,7 +2305,7 @@ public static void ChangePassword(string connectionString, string newPassword) SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null); SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key); - if (connectionOptions.IntegratedSecurity) + if (connectionOptions.IntegratedSecurity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { throw SQL.ChangePasswordConflictsWithSSPI(); } @@ -2193,11 +2314,16 @@ public static void ChangePassword(string connectionString, string newPassword) throw SQL.ChangePasswordUseOfUnallowedKey(DbConnectionStringKeywords.AttachDbFilename); } +#if NETFRAMEWORK + PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); + permissionSet.Demand(); +#endif + ChangePassword(connectionString, connectionOptions, null, newPassword, null); } } - /// + /// public static void ChangePassword(string connectionString, SqlCredential credential, SecureString newSecurePassword) { using (TryEventScope.Create("SqlConnection.ChangePassword | API | Password change requested.")) @@ -2250,6 +2376,11 @@ public static void ChangePassword(string connectionString, SqlCredential credent throw SQL.ChangePasswordUseOfUnallowedKey(DbConnectionStringKeywords.AttachDbFilename); } +#if NETFRAMEWORK + PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); + permissionSet.Demand(); +#endif + ChangePassword(connectionString, connectionOptions, credential, null, newSecurePassword); } } @@ -2318,7 +2449,7 @@ internal Task RegisterForConnectionCloseNotification(Task outerTask, ob ).Unwrap(); } - /// + /// public void ResetStatistics() { if (Statistics != null) @@ -2332,7 +2463,7 @@ public void ResetStatistics() } } - /// + /// public IDictionary RetrieveStatistics() { if (Statistics != null) @@ -2357,7 +2488,7 @@ private void UpdateStatistics() Statistics.UpdateStatistics(); } - /// + /// public IDictionary RetrieveInternalInfo() { IDictionary internalDictionary = new Dictionary(); @@ -2368,7 +2499,7 @@ public IDictionary RetrieveInternalInfo() return internalDictionary; } - /// + /// object ICloneable.Clone() => new SqlConnection(this); private void CopyFrom(SqlConnection connection) @@ -2377,6 +2508,9 @@ private void CopyFrom(SqlConnection connection) _userConnectionOptions = connection.UserConnectionOptions; _poolGroup = connection.PoolGroup; + // SQLBU 432115 + // Match the original connection's behavior for whether the connection was never opened, + // but ensure Clone is in the closed state. if (DbConnectionClosedNeverOpened.SingletonInstance == connection._innerConnection) { _innerConnection = DbConnectionClosedNeverOpened.SingletonInstance; diff --git a/tools/targets/NotSupported.targets b/tools/targets/NotSupported.targets index 124d86f87c..c200014ada 100644 --- a/tools/targets/NotSupported.targets +++ b/tools/targets/NotSupported.targets @@ -8,6 +8,7 @@ false false + $(NoWarn);CS0618