From 46a5d0f1635ac48b2e7e7c68745b4f6902099ad9 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 29 Sep 2025 09:11:30 -0700 Subject: [PATCH 01/10] Backport fix. --- .../Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- .../SqlClient/SqlInternalConnectionTds.cs | 72 +++-- .../Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- .../SqlClient/SqlInternalConnectionTds.cs | 68 +++-- .../Data/SqlClient/LocalAppContextSwitches.cs | 245 +++++++++++++++++- 5 files changed, 340 insertions(+), 49 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index e64d81da40..a3713fcc87 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -3611,7 +3611,7 @@ private void CheckNotificationStateAndAutoEnlist() } Notification.Options = SqlDependency.GetDefaultComposedOptions(_activeConnection.DataSource, - InternalTdsConnection.ServerProvidedFailOverPartner, + InternalTdsConnection.ServerProvidedFailoverPartner, identityUserName, _activeConnection.Database); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 1f7cb869b1..ffccc1e86b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -307,7 +307,6 @@ internal SessionData CurrentSessionData // FOR CONNECTION RESET MANAGEMENT private bool _fResetConnection; private string _originalDatabase; - private string _currentFailoverPartner; // only set by ENV change from server private string _originalLanguage; private string _currentLanguage; private int _currentPacketSize; @@ -676,13 +675,7 @@ internal TdsParser Parser } } - internal string ServerProvidedFailOverPartner - { - get - { - return _currentFailoverPartner; - } - } + internal string ServerProvidedFailoverPartner { get; set; } internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo { @@ -1461,7 +1454,7 @@ private void OpenLoginEnlist(TimeoutTimer timeout, throw SQL.ROR_FailoverNotSupportedConnString(); } - if (ServerProvidedFailOverPartner != null) + if (ServerProvidedFailoverPartner != null) { throw SQL.ROR_FailoverNotSupportedServer(this); } @@ -1574,7 +1567,7 @@ private void LoginNoFailover(ServerInfo serverInfo, newSecurePassword, connectionOptions.MultiSubnetFailover ? intervalTimer : timeout); - if (connectionOptions.MultiSubnetFailover && ServerProvidedFailOverPartner != null) + if (connectionOptions.MultiSubnetFailover && ServerProvidedFailoverPartner != null) { // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this); @@ -1602,7 +1595,7 @@ private void LoginNoFailover(ServerInfo serverInfo, _currentPacketSize = ConnectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog; - _currentFailoverPartner = null; + ServerProvidedFailoverPartner = null; _instanceName = string.Empty; routingAttempts++; @@ -1641,7 +1634,7 @@ private void LoginNoFailover(ServerInfo serverInfo, // We only get here when we failed to connect, but are going to re-try // Switch to failover logic if the server provided a partner - if (ServerProvidedFailOverPartner != null) + if (ServerProvidedFailoverPartner != null) { if (connectionOptions.MultiSubnetFailover) { @@ -1657,7 +1650,7 @@ private void LoginNoFailover(ServerInfo serverInfo, LoginWithFailover( true, // start by using failover partner, since we already failed to connect to the primary serverInfo, - ServerProvidedFailOverPartner, + ServerProvidedFailoverPartner, newPassword, newSecurePassword, redirectedUserInstance, @@ -1679,8 +1672,13 @@ private void LoginNoFailover(ServerInfo serverInfo, { // We must wait for CompleteLogin to finish for to have the // env change from the server to know its designated failover - // partner; save this information in _currentFailoverPartner. - PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, ServerProvidedFailOverPartner); + // partner; save this information in ServerProvidedFailoverPartner. + + // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string. + // Otherwise the pool group's failover partner designation will be updated to point to the server provided value. + string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? "" : ServerProvidedFailoverPartner; + + PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, actualFailoverPartner); } CurrentDataSource = originalServerInfo.UserServerName; } @@ -1741,7 +1739,7 @@ TimeoutTimer timeout ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN); ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); - if (ServerProvidedFailOverPartner == null) + if (ServerProvidedFailoverPartner == null) { ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions); } @@ -1791,12 +1789,34 @@ TimeoutTimer timeout ServerInfo currentServerInfo; if (useFailoverHost) { +<<<<<<< HEAD // Primary server may give us a different failover partner than the connection string indicates. Update it if (ServerProvidedFailOverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) +======= + if (!failoverDemandDone) { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, new failover partner={1}", ObjectID, ServerProvidedFailOverPartner); - failoverServerInfo.SetDerivedNames(string.Empty, ServerProvidedFailOverPartner); +#if NETFRAMEWORK + FailoverPermissionDemand(); +#endif + failoverDemandDone = true; } + + // Primary server may give us a different failover partner than the connection string indicates. + // Update it only if we are respecting server-provided failover partner values. + if (ServerProvidedFailoverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailoverPartner) +>>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) + { + if (LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Ignoring server provided failover partner '{1}' due to IgnoreServerProvidedFailoverPartner AppContext switch.", ObjectID, ServerProvidedFailoverPartner); + } + else + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, new failover partner={1}", ObjectID, ServerProvidedFailoverPartner); + failoverServerInfo.SetDerivedNames(string.Empty, ServerProvidedFailoverPartner); + } + } + currentServerInfo = failoverServerInfo; _timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover); } @@ -1845,7 +1865,7 @@ TimeoutTimer timeout _currentPacketSize = connectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog; - _currentFailoverPartner = null; + ServerProvidedFailoverPartner = null; _instanceName = string.Empty; AttemptOneLogin( @@ -1906,7 +1926,7 @@ TimeoutTimer timeout _activeDirectoryAuthTimeoutRetryHelper.State = ActiveDirectoryAuthenticationTimeoutRetryState.HasLoggedIn; // if connected to failover host, but said host doesn't have DbMirroring set up, throw an error - if (useFailoverHost && ServerProvidedFailOverPartner == null) + if (useFailoverHost && ServerProvidedFailoverPartner == null) { throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase); } @@ -1915,8 +1935,13 @@ TimeoutTimer timeout { // We must wait for CompleteLogin to finish for to have the // env change from the server to know its designated failover - // partner; save this information in _currentFailoverPartner. - PoolGroupProviderInfo.FailoverCheck(useFailoverHost, connectionOptions, ServerProvidedFailOverPartner); + // partner. + + // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string. + // Otherwise the pool group's failover partner designation will be updated to point to the server provided value. + string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? failoverHost : ServerProvidedFailoverPartner; + + PoolGroupProviderInfo.FailoverCheck(useFailoverHost, connectionOptions, actualFailoverPartner); } CurrentDataSource = (useFailoverHost ? failoverHost : primaryServerInfo.UserServerName); } @@ -2138,7 +2163,8 @@ internal void OnEnvChange(SqlEnvChange rec) { throw SQL.ROR_FailoverNotSupportedServer(this); } - _currentFailoverPartner = rec._newValue; + + ServerProvidedFailoverPartner = rec._newValue; break; case TdsEnums.ENV_PROMOTETRANSACTION: diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index f66d9a811d..4425898b8c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -3899,7 +3899,7 @@ private void CheckNotificationStateAndAutoEnlist() } Notification.Options = SqlDependency.GetDefaultComposedOptions(_activeConnection.DataSource, - InternalTdsConnection.ServerProvidedFailOverPartner, + InternalTdsConnection.ServerProvidedFailoverPartner, identityUserName, _activeConnection.Database); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index d0c64d07e3..9c718f0759 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -275,7 +275,6 @@ internal SessionData CurrentSessionData // FOR CONNECTION RESET MANAGEMENT private bool _fResetConnection; private string _originalDatabase; - private string _currentFailoverPartner; // only set by ENV change from server private string _originalLanguage; private string _currentLanguage; private int _currentPacketSize; @@ -771,13 +770,7 @@ internal TdsParser Parser } } - internal string ServerProvidedFailOverPartner - { - get - { - return _currentFailoverPartner; - } - } + internal string ServerProvidedFailoverPartner { get; set; } internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo { @@ -1711,7 +1704,7 @@ private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectio throw SQL.ROR_FailoverNotSupportedConnString(); } - if (ServerProvidedFailOverPartner != null) + if (ServerProvidedFailoverPartner != null) { throw SQL.ROR_FailoverNotSupportedServer(this); } @@ -1860,7 +1853,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt isFirstTransparentAttempt: isFirstTransparentAttempt, disableTnir: disableTnir); - if (connectionOptions.MultiSubnetFailover && ServerProvidedFailOverPartner != null) + if (connectionOptions.MultiSubnetFailover && ServerProvidedFailoverPartner != null) { // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used. throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this); @@ -1889,7 +1882,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt _currentPacketSize = ConnectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog; - _currentFailoverPartner = null; + ServerProvidedFailoverPartner = null; _instanceName = string.Empty; routingAttempts++; @@ -1929,7 +1922,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt // We only get here when we failed to connect, but are going to re-try // Switch to failover logic if the server provided a partner - if (ServerProvidedFailOverPartner != null) + if (ServerProvidedFailoverPartner != null) { if (connectionOptions.MultiSubnetFailover) { @@ -1945,7 +1938,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt LoginWithFailover( true, // start by using failover partner, since we already failed to connect to the primary serverInfo, - ServerProvidedFailOverPartner, + ServerProvidedFailoverPartner, newPassword, newSecurePassword, redirectedUserInstance, @@ -1968,8 +1961,13 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt { // We must wait for CompleteLogin to finish for to have the // env change from the server to know its designated failover - // partner; save this information in _currentFailoverPartner. - PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, ServerProvidedFailOverPartner); + // partner; save this information in ServerProvidedFailoverPartner. + + // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string. + // Otherwise the pool group's failover partner designation will be updated to point to the server provided value. + string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? "" : ServerProvidedFailoverPartner; + + PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, actualFailoverPartner); } CurrentDataSource = originalServerInfo.UserServerName; } @@ -2054,9 +2052,14 @@ TimeoutTimer timeout ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN); ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); +<<<<<<< HEAD if (ServerProvidedFailOverPartner == null) {// No point in resolving the failover partner when we're going to override it below // Don't resolve aliases if failover == primary // UNDONE: WHY? Previous code in TdsParser.Connect did this, but the reason is not clear +======= + if (ServerProvidedFailoverPartner == null) + { +>>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions); } @@ -2112,12 +2115,21 @@ TimeoutTimer timeout failoverDemandDone = true; } - // Primary server may give us a different failover partner than the connection string indicates. Update it - if (ServerProvidedFailOverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) + // Primary server may give us a different failover partner than the connection string indicates. + // Update it only if we are respecting server-provided failover partner values. + if (ServerProvidedFailoverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailoverPartner) { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, new failover partner={1}", ObjectID, ServerProvidedFailOverPartner); - failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner); + if (LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Ignoring server provided failover partner '{1}' due to IgnoreServerProvidedFailoverPartner AppContext switch.", ObjectID, ServerProvidedFailoverPartner); + } + else + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, new failover partner={1}", ObjectID, ServerProvidedFailoverPartner); + failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailoverPartner); + } } + currentServerInfo = failoverServerInfo; _timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover); } @@ -2165,8 +2177,13 @@ TimeoutTimer timeout // restore properties that could be changed by the environment tokens _currentPacketSize = ConnectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; +<<<<<<< HEAD CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog; _currentFailoverPartner = null; +======= + CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog; + ServerProvidedFailoverPartner = null; +>>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) _instanceName = string.Empty; AttemptOneLogin( @@ -2231,7 +2248,7 @@ TimeoutTimer timeout _activeDirectoryAuthTimeoutRetryHelper.State = ActiveDirectoryAuthenticationTimeoutRetryState.HasLoggedIn; // if connected to failover host, but said host doesn't have DbMirroring set up, throw an error - if (useFailoverHost && ServerProvidedFailOverPartner == null) + if (useFailoverHost && ServerProvidedFailoverPartner == null) { throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase); } @@ -2240,8 +2257,13 @@ TimeoutTimer timeout { // We must wait for CompleteLogin to finish for to have the // env change from the server to know its designated failover - // partner; save this information in _currentFailoverPartner. - PoolGroupProviderInfo.FailoverCheck(useFailoverHost, connectionOptions, ServerProvidedFailOverPartner); + // partner. + + // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string. + // Otherwise the pool group's failover partner designation will be updated to point to the server provided value. + string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? failoverHost : ServerProvidedFailoverPartner; + + PoolGroupProviderInfo.FailoverCheck(useFailoverHost, connectionOptions, actualFailoverPartner); } CurrentDataSource = (useFailoverHost ? failoverHost : primaryServerInfo.UserServerName); } @@ -2499,7 +2521,7 @@ internal void OnEnvChange(SqlEnvChange rec) break; case TdsEnums.ENV_LOGSHIPNODE: - _currentFailoverPartner = rec._newValue; + ServerProvidedFailoverPartner = rec._newValue; break; case TdsEnums.ENV_PROMOTETRANSACTION: diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index 9ea289f5b7..b7f9836586 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -15,11 +15,31 @@ private enum Tristate : byte True = 2 } +<<<<<<< HEAD internal const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; internal const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; internal const string SuppressInsecureTLSWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; internal const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; internal const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; +======= + private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; + private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; + private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; + private const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; + private const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; + private const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; + private const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; + private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; + private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; +#if NET + private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; + private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; + private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; +#else + private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; +#endif +>>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) // this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests private static Tristate s_legacyRowVersionNullBehavior; @@ -28,6 +48,20 @@ private enum Tristate : byte private static Tristate s_useMinimumLoginTimeout; // this field is accessed through reflection in Microsoft.Data.SqlClient.Tests.SqlParameterTests and should not be renamed or have the type changed without refactoring related tests private static Tristate s_legacyVarTimeZeroScaleBehaviour; +<<<<<<< HEAD +======= + private static Tristate s_useCompatibilityProcessSni; + private static Tristate s_useCompatibilityAsyncBehaviour; + private static Tristate s_useConnectionPoolV2; + private static Tristate s_truncateScaledDecimal; + private static Tristate s_ignoreServerProvidedFailoverPartner; +#if NET + private static Tristate s_globalizationInvariantMode; + private static Tristate s_useManagedNetworking; +#else + private static Tristate s_disableTnirByDefault; +#endif +>>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) #if NET static LocalAppContextSwitches() @@ -185,7 +219,7 @@ public static bool UseMinimumLoginTimeout /// When set to 'true' this will output a scale value of 7 (DEFAULT_VARTIME_SCALE) when the scale /// is explicitly set to zero for VarTime data types ('datetime2', 'datetimeoffset' and 'time') /// If no scale is set explicitly it will continue to output scale of 7 (DEFAULT_VARTIME_SCALE) - /// regardsless of switch value. + /// regardless of switch value. /// This app context switch defaults to 'true'. /// public static bool LegacyVarTimeZeroScaleBehaviour @@ -206,5 +240,214 @@ public static bool LegacyVarTimeZeroScaleBehaviour return s_legacyVarTimeZeroScaleBehaviour == Tristate.True; } } +<<<<<<< HEAD +======= + + /// + /// When set to true, the connection pool will use the new V2 connection pool implementation. + /// When set to false, the connection pool will use the legacy V1 implementation. + /// This app context switch defaults to 'false'. + /// + public static bool UseConnectionPoolV2 + { + get + { + if (s_useConnectionPoolV2 == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(UseConnectionPoolV2String, out bool returnedValue) && returnedValue) + { + s_useConnectionPoolV2 = Tristate.True; + } + else + { + s_useConnectionPoolV2 = Tristate.False; + } + } + return s_useConnectionPoolV2 == Tristate.True; + } + } + + /// + /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. + /// + public static bool TruncateScaledDecimal + { + get + { + if (s_truncateScaledDecimal == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(TruncateScaledDecimalString, out bool returnedValue) && returnedValue) + { + s_truncateScaledDecimal = Tristate.True; + } + else + { + s_truncateScaledDecimal = Tristate.False; + } + } + return s_truncateScaledDecimal == Tristate.True; + } + } + + /// + /// When set to true, the failover partner provided by the server during connection + /// will be ignored. This is useful in scenarios where the application wants to + /// control the failover behavior explicitly (e.g. using a custom port). The application + /// must be kept up to date with the failover configuration of the server. + /// The application will not automatically discover a newly configured failover partner. + /// + /// This app context switch defaults to 'false'. + /// + public static bool IgnoreServerProvidedFailoverPartner + { + get + { + if (s_ignoreServerProvidedFailoverPartner == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(IgnoreServerProvidedFailoverPartnerString, out bool returnedValue) && returnedValue) + { + s_ignoreServerProvidedFailoverPartner = Tristate.True; + } + else + { + s_ignoreServerProvidedFailoverPartner = Tristate.False; + } + } + return s_ignoreServerProvidedFailoverPartner == Tristate.True; + } + } + +#if NET + /// + /// .NET Core 2.0 and up supports Globalization Invariant mode, which reduces the size of the required libraries for + /// applications which don't need globalization support. SqlClient requires those libraries for core functionality, + /// and will throw exceptions later if they are not present. This switch allows SqlClient to detect this mode early. + /// + public static bool GlobalizationInvariantMode + { + get + { + if (s_globalizationInvariantMode == Tristate.NotInitialized) + { + // Check if invariant mode is has been set by the AppContext switch directly + if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue) + { + s_globalizationInvariantMode = Tristate.True; + } + else + { + // If the switch is not set, we check the environment variable as the first fallback + string envValue = Environment.GetEnvironmentVariable(GlobalizationInvariantModeEnvironmentVariable); + + if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) + { + s_globalizationInvariantMode = Tristate.True; + } + else + { + // If this hasn't been manually set, it could still apply if the OS doesn't have ICU libraries installed, + // or if the application is a native binary with ICU support trimmed away. + // .NET 3.1 to 5.0 do not throw in attempting to create en-US in invariant mode, but .NET 6+ does. In + // such cases, catch and infer invariant mode from the exception. + try + { + s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") + ? Tristate.True + : Tristate.False; + } + catch (System.Globalization.CultureNotFoundException) + { + // If the culture is not found, it means we are in invariant mode + s_globalizationInvariantMode = Tristate.True; + } + } + } + } + return s_globalizationInvariantMode == Tristate.True; + } + } + + /// + /// When set to true, .NET Core will use the managed SNI implementation instead of the native SNI implementation. + /// + /// + /// + /// Non-Windows platforms will always use the managed networking implementation. Windows platforms will use the native SNI + /// implementation by default, but this can be overridden by setting the AppContext switch. + /// + /// + /// ILLink.Substitutions.xml allows the unused SNI implementation to be trimmed away when the corresponding AppContext + /// switch is set at compile time. In such cases, this property will return a constant value, even if the AppContext switch is + /// set or reset at runtime. See the ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml resource files for details. + /// + /// + public static bool UseManagedNetworking + { + get + { + if (s_useManagedNetworking == Tristate.NotInitialized) + { + if (!OperatingSystem.IsWindows()) + { + s_useManagedNetworking = Tristate.True; + } + else if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) + { + s_useManagedNetworking = Tristate.True; + } + else + { + s_useManagedNetworking = Tristate.False; + } + } + return s_useManagedNetworking == Tristate.True; + } + } +#else + /// + /// .NET Framework does not support Globalization Invariant mode, so this will always be false. + /// + public const bool GlobalizationInvariantMode = false; + + /// + /// .NET Framework does not support the managed SNI, so this will always be false. + /// + public const bool UseManagedNetworking = false; + + /// + /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. + /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname + /// doesn't respond and there are multiple IPs associated with the hostname. + /// + /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: + /// 0: One IP is attempted, followed by all IPs in parallel + /// 1: All IPs are attempted in parallel + /// 2: All IPs are attempted one after another + /// + /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. + /// To disable TNIR, you can enable the app context switch. + /// + /// This app context switch defaults to 'false'. + /// + public static bool DisableTnirByDefault + { + get + { + if (s_disableTnirByDefault == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) + { + s_disableTnirByDefault = Tristate.True; + } + else + { + s_disableTnirByDefault = Tristate.False; + } + } + return s_disableTnirByDefault == Tristate.True; + } + } +#endif +>>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) } } From 574dcb149b328c0519aa7feeb93e496814559a6e Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 20 Oct 2025 13:58:47 -0700 Subject: [PATCH 02/10] Fix merge conflicts. --- .../SqlClient/SqlInternalConnectionTds.cs | 13 -- .../SqlClient/SqlInternalConnectionTds.cs | 13 +- .../Data/SqlClient/LocalAppContextSwitches.cs | 213 ------------------ 3 files changed, 2 insertions(+), 237 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index ffccc1e86b..40a12a686e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1789,22 +1789,9 @@ TimeoutTimer timeout ServerInfo currentServerInfo; if (useFailoverHost) { -<<<<<<< HEAD - // Primary server may give us a different failover partner than the connection string indicates. Update it - if (ServerProvidedFailOverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) -======= - if (!failoverDemandDone) - { -#if NETFRAMEWORK - FailoverPermissionDemand(); -#endif - failoverDemandDone = true; - } - // Primary server may give us a different failover partner than the connection string indicates. // Update it only if we are respecting server-provided failover partner values. if (ServerProvidedFailoverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailoverPartner) ->>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) { if (LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 9c718f0759..8f50a63e26 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2052,14 +2052,10 @@ TimeoutTimer timeout ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN); ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); -<<<<<<< HEAD - if (ServerProvidedFailOverPartner == null) - {// No point in resolving the failover partner when we're going to override it below - // Don't resolve aliases if failover == primary // UNDONE: WHY? Previous code in TdsParser.Connect did this, but the reason is not clear -======= if (ServerProvidedFailoverPartner == null) { ->>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) + // No point in resolving the failover partner when we're going to override it below + // Don't resolve aliases if failover == primary // UNDONE: WHY? Previous code in TdsParser.Connect did this, but the reason is not clear ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions); } @@ -2177,13 +2173,8 @@ TimeoutTimer timeout // restore properties that could be changed by the environment tokens _currentPacketSize = ConnectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; -<<<<<<< HEAD CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog; - _currentFailoverPartner = null; -======= - CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog; ServerProvidedFailoverPartner = null; ->>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) _instanceName = string.Empty; AttemptOneLogin( diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index b7f9836586..afb1a95a8a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -15,31 +15,12 @@ private enum Tristate : byte True = 2 } -<<<<<<< HEAD internal const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; internal const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; internal const string SuppressInsecureTLSWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; internal const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; internal const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; -======= - private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; - private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; - private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; - private const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; - private const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; - private const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; - private const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; - private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; - private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; -#if NET - private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; - private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; - private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; -#else - private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; -#endif ->>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) // this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests private static Tristate s_legacyRowVersionNullBehavior; @@ -48,20 +29,7 @@ private enum Tristate : byte private static Tristate s_useMinimumLoginTimeout; // this field is accessed through reflection in Microsoft.Data.SqlClient.Tests.SqlParameterTests and should not be renamed or have the type changed without refactoring related tests private static Tristate s_legacyVarTimeZeroScaleBehaviour; -<<<<<<< HEAD -======= - private static Tristate s_useCompatibilityProcessSni; - private static Tristate s_useCompatibilityAsyncBehaviour; - private static Tristate s_useConnectionPoolV2; - private static Tristate s_truncateScaledDecimal; private static Tristate s_ignoreServerProvidedFailoverPartner; -#if NET - private static Tristate s_globalizationInvariantMode; - private static Tristate s_useManagedNetworking; -#else - private static Tristate s_disableTnirByDefault; -#endif ->>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) #if NET static LocalAppContextSwitches() @@ -240,54 +208,6 @@ public static bool LegacyVarTimeZeroScaleBehaviour return s_legacyVarTimeZeroScaleBehaviour == Tristate.True; } } -<<<<<<< HEAD -======= - - /// - /// When set to true, the connection pool will use the new V2 connection pool implementation. - /// When set to false, the connection pool will use the legacy V1 implementation. - /// This app context switch defaults to 'false'. - /// - public static bool UseConnectionPoolV2 - { - get - { - if (s_useConnectionPoolV2 == Tristate.NotInitialized) - { - if (AppContext.TryGetSwitch(UseConnectionPoolV2String, out bool returnedValue) && returnedValue) - { - s_useConnectionPoolV2 = Tristate.True; - } - else - { - s_useConnectionPoolV2 = Tristate.False; - } - } - return s_useConnectionPoolV2 == Tristate.True; - } - } - - /// - /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. - /// - public static bool TruncateScaledDecimal - { - get - { - if (s_truncateScaledDecimal == Tristate.NotInitialized) - { - if (AppContext.TryGetSwitch(TruncateScaledDecimalString, out bool returnedValue) && returnedValue) - { - s_truncateScaledDecimal = Tristate.True; - } - else - { - s_truncateScaledDecimal = Tristate.False; - } - } - return s_truncateScaledDecimal == Tristate.True; - } - } /// /// When set to true, the failover partner provided by the server during connection @@ -316,138 +236,5 @@ public static bool IgnoreServerProvidedFailoverPartner return s_ignoreServerProvidedFailoverPartner == Tristate.True; } } - -#if NET - /// - /// .NET Core 2.0 and up supports Globalization Invariant mode, which reduces the size of the required libraries for - /// applications which don't need globalization support. SqlClient requires those libraries for core functionality, - /// and will throw exceptions later if they are not present. This switch allows SqlClient to detect this mode early. - /// - public static bool GlobalizationInvariantMode - { - get - { - if (s_globalizationInvariantMode == Tristate.NotInitialized) - { - // Check if invariant mode is has been set by the AppContext switch directly - if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue) - { - s_globalizationInvariantMode = Tristate.True; - } - else - { - // If the switch is not set, we check the environment variable as the first fallback - string envValue = Environment.GetEnvironmentVariable(GlobalizationInvariantModeEnvironmentVariable); - - if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) - { - s_globalizationInvariantMode = Tristate.True; - } - else - { - // If this hasn't been manually set, it could still apply if the OS doesn't have ICU libraries installed, - // or if the application is a native binary with ICU support trimmed away. - // .NET 3.1 to 5.0 do not throw in attempting to create en-US in invariant mode, but .NET 6+ does. In - // such cases, catch and infer invariant mode from the exception. - try - { - s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") - ? Tristate.True - : Tristate.False; - } - catch (System.Globalization.CultureNotFoundException) - { - // If the culture is not found, it means we are in invariant mode - s_globalizationInvariantMode = Tristate.True; - } - } - } - } - return s_globalizationInvariantMode == Tristate.True; - } - } - - /// - /// When set to true, .NET Core will use the managed SNI implementation instead of the native SNI implementation. - /// - /// - /// - /// Non-Windows platforms will always use the managed networking implementation. Windows platforms will use the native SNI - /// implementation by default, but this can be overridden by setting the AppContext switch. - /// - /// - /// ILLink.Substitutions.xml allows the unused SNI implementation to be trimmed away when the corresponding AppContext - /// switch is set at compile time. In such cases, this property will return a constant value, even if the AppContext switch is - /// set or reset at runtime. See the ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml resource files for details. - /// - /// - public static bool UseManagedNetworking - { - get - { - if (s_useManagedNetworking == Tristate.NotInitialized) - { - if (!OperatingSystem.IsWindows()) - { - s_useManagedNetworking = Tristate.True; - } - else if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) - { - s_useManagedNetworking = Tristate.True; - } - else - { - s_useManagedNetworking = Tristate.False; - } - } - return s_useManagedNetworking == Tristate.True; - } - } -#else - /// - /// .NET Framework does not support Globalization Invariant mode, so this will always be false. - /// - public const bool GlobalizationInvariantMode = false; - - /// - /// .NET Framework does not support the managed SNI, so this will always be false. - /// - public const bool UseManagedNetworking = false; - - /// - /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. - /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname - /// doesn't respond and there are multiple IPs associated with the hostname. - /// - /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: - /// 0: One IP is attempted, followed by all IPs in parallel - /// 1: All IPs are attempted in parallel - /// 2: All IPs are attempted one after another - /// - /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. - /// To disable TNIR, you can enable the app context switch. - /// - /// This app context switch defaults to 'false'. - /// - public static bool DisableTnirByDefault - { - get - { - if (s_disableTnirByDefault == Tristate.NotInitialized) - { - if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) - { - s_disableTnirByDefault = Tristate.True; - } - else - { - s_disableTnirByDefault = Tristate.False; - } - } - return s_disableTnirByDefault == Tristate.True; - } - } -#endif ->>>>>>> fa7132eb7 (Fix #3400, ignore server-provided failover partner (#3625)) } } From 66dc42fcc58a097214a8262b831641e896a5a509 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 22 Oct 2025 11:12:35 -0700 Subject: [PATCH 03/10] Add test case. Doesn't compile. --- .../SqlConnectionBasicTests.cs | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index 6bbc44f6b1..b1694a562b 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -12,7 +12,7 @@ using System.Security; using System.Threading; using System.Threading.Tasks; -using Microsoft.SqlServer.TDS.PreLogin; +using Microsoft.SqlServer.TDS.EndPoint; using Microsoft.SqlServer.TDS.Servers; using Xunit; @@ -295,6 +295,69 @@ public void ConnectionTestValidCredentialCombination() Assert.Equal(sqlCredential, conn.Credential); } + [Fact] + public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() + { + // Arrange + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.IgnoreServerProvidedFailoverPartnerField = LocalAppContextSwitchesHelper.Tristate.True; + + using TdsServer failoverServer = new( + new TdsServerArguments + { + // Doesn't need to point to a real endpoint, just needs a value specified + FailoverPartner = "localhost,1234", + }); + failoverServer.Start(); + + using TdsServer server = new( + new TdsServerArguments() + { + // Set an invalid failover partner to ensure that the connection fails if the + // server provided failover partner is used. + FailoverPartner = $"invalidhost", + }); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + InitialCatalog = "master", + Encrypt = false, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + // Ensure pooling is enabled so that the failover partner information + // is persisted in the pool group. If pooling is disabled, the server + // provided failover partner will never be used. + Pooling = true + }; + SqlConnection connection = new(builder.ConnectionString); + + // Connect once to the primary to trigger it to send the failover partner + connection.Open(); + Assert.Equal("invalidhost", (connection.InnerConnection as SqlInternalConnectionTds)!.ServerProvidedFailoverPartner); + + // Close the connection to return it to the pool + connection.Close(); + + + // Act + // Dispose of the server to trigger a failover + server.Dispose(); + + // Opening a new connection will use the failover partner stored in the pool group. + // This will fail if the server provided failover partner was stored to the pool group. + using SqlConnection failoverConnection = new(builder.ConnectionString); + failoverConnection.Open(); + + // Assert + Assert.Equal(ConnectionState.Open, failoverConnection.State); + Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", failoverConnection.DataSource); + // 1 for the initial connection + Assert.Equal(1, server.PreLoginCount); + // 1 for the failover connection + Assert.Equal(1, failoverServer.PreLoginCount); + } + [Theory] [InlineData(60)] From c85d897382b9cc9eade425c8914bde03e3029313 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 22 Oct 2025 11:17:49 -0700 Subject: [PATCH 04/10] Adjust usage of test servers and app context. --- .../SqlConnectionBasicTests.cs | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index b1694a562b..2949868734 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -299,32 +299,24 @@ public void ConnectionTestValidCredentialCombination() public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() { // Arrange - using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.IgnoreServerProvidedFailoverPartnerField = LocalAppContextSwitchesHelper.Tristate.True; + AppContext.SetSwitch("Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner", true); - using TdsServer failoverServer = new( - new TdsServerArguments - { - // Doesn't need to point to a real endpoint, just needs a value specified - FailoverPartner = "localhost,1234", - }); - failoverServer.Start(); + using TestTdsServer failoverServer = TestTdsServer.StartTestServer(); + // Doesn't need to point to a real endpoint, just needs a value specified + //TODO: FailoverPartner = "localhost,1234", - using TdsServer server = new( - new TdsServerArguments() - { + var failoverBuilder = new SqlConnectionStringBuilder(failoverServer.ConnectionString); + + using TestTdsServer server = TestTdsServer.StartTestServer(); // Set an invalid failover partner to ensure that the connection fails if the // server provided failover partner is used. - FailoverPartner = $"invalidhost", - }); - server.Start(); + // TODO: FailoverPartner = $"invalidhost", - SqlConnectionStringBuilder builder = new() + SqlConnectionStringBuilder builder = new(server.ConnectionString) { - DataSource = $"localhost,{server.EndPoint.Port}", InitialCatalog = "master", Encrypt = false, - FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + FailoverPartner = failoverBuilder.DataSource, // Ensure pooling is enabled so that the failover partner information // is persisted in the pool group. If pooling is disabled, the server // provided failover partner will never be used. @@ -351,7 +343,7 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // Assert Assert.Equal(ConnectionState.Open, failoverConnection.State); - Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", failoverConnection.DataSource); + Assert.Equal(failoverBuilder.DataSource, failoverConnection.DataSource); // 1 for the initial connection Assert.Equal(1, server.PreLoginCount); // 1 for the failover connection From c631c92af96d3ea51b951482ee32407ac3a11906 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 22 Oct 2025 11:54:50 -0700 Subject: [PATCH 05/10] Add failover and prelogin count capabilities to test server. Update test case. --- .../SqlConnectionBasicTests.cs | 26 +++++++++++++------ .../tools/TDS/TDS.Servers/GenericTDSServer.cs | 24 ++++++++++++++++- .../TDS/TDS.Servers/TDSServerArguments.cs | 5 ++++ 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index 2949868734..8715bd138b 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Configuration; using System.Data; using System.Data.Common; using System.Diagnostics; @@ -20,6 +21,13 @@ namespace Microsoft.Data.SqlClient.Tests { public class SqlConnectionBasicTests { + // Reflection + public static Assembly systemData = Assembly.GetAssembly(typeof(SqlConnection)); + public static Type sqlConnection = systemData.GetType("Microsoft.Data.SqlClient.SqlConnection"); + public static PropertyInfo innerConnectionProperty = sqlConnection.GetProperty("InnerConnection", BindingFlags.NonPublic | BindingFlags.Instance); + public static Type sqlInternalConnectionTds = systemData.GetType("Microsoft.Data.SqlClient.SqlInternalConnectionTds"); + public static PropertyInfo serverProvidedFailoverPartnerProperty = sqlInternalConnectionTds.GetProperty("ServerProvidedFailoverPartner", BindingFlags.NonPublic | BindingFlags.Instance); + [Fact] public void ConnectionTest() { @@ -299,18 +307,18 @@ public void ConnectionTestValidCredentialCombination() public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() { // Arrange - AppContext.SetSwitch("Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner", true); + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner", true); using TestTdsServer failoverServer = TestTdsServer.StartTestServer(); // Doesn't need to point to a real endpoint, just needs a value specified - //TODO: FailoverPartner = "localhost,1234", + failoverServer.Arguments.FailoverPartner = "localhost,1234"; var failoverBuilder = new SqlConnectionStringBuilder(failoverServer.ConnectionString); using TestTdsServer server = TestTdsServer.StartTestServer(); - // Set an invalid failover partner to ensure that the connection fails if the - // server provided failover partner is used. - // TODO: FailoverPartner = $"invalidhost", + // Set an invalid failover partner to ensure that the connection fails if the + // server provided failover partner is used. + server.Arguments.FailoverPartner = $"invalidhost"; SqlConnectionStringBuilder builder = new(server.ConnectionString) { @@ -320,18 +328,20 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // Ensure pooling is enabled so that the failover partner information // is persisted in the pool group. If pooling is disabled, the server // provided failover partner will never be used. - Pooling = true + Pooling = true, }; SqlConnection connection = new(builder.ConnectionString); // Connect once to the primary to trigger it to send the failover partner connection.Open(); - Assert.Equal("invalidhost", (connection.InnerConnection as SqlInternalConnectionTds)!.ServerProvidedFailoverPartner); + + var innerConnection = innerConnectionProperty.GetValue(connection); + var serverProvidedFailoverPartner = serverProvidedFailoverPartnerProperty.GetValue(innerConnection); + Assert.Equal("invalidhost", serverProvidedFailoverPartner); // Close the connection to return it to the pool connection.Close(); - // Act // Dispose of the server to trigger a failover server.Dispose(); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs index a214c51f88..24fbc8c7dc 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs @@ -32,16 +32,26 @@ public class GenericTDSServer : ITDSServer /// private int _sessionCount = 0; + /// + /// Counts pre-login requests to the server. + /// + private int _preLoginCount = 0; + /// /// Server configuration /// - protected TDSServerArguments Arguments { get; set; } + public TDSServerArguments Arguments { get; set; } /// /// Query engine instance /// protected QueryEngine Engine { get; set; } + /// + /// Counts pre-login requests to the server. + /// + public int PreLoginCount => _preLoginCount; + /// /// Default constructor /// @@ -105,6 +115,8 @@ public virtual void CloseSession(ITDSServerSession session) /// public virtual TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, TDSMessage request) { + Interlocked.Increment(ref _preLoginCount); + // Inflate pre-login request from the message TDSPreLoginToken preLoginRequest = request[0] as TDSPreLoginToken; @@ -577,6 +589,16 @@ protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSessi } } + if (!string.IsNullOrEmpty(Arguments.FailoverPartner)) + { + envChange = new TDSEnvChangeToken(TDSEnvChangeTokenType.RealTimeLogShipping, Arguments.FailoverPartner); + + // Log response + TDSUtilities.Log(Arguments.Log, "Response", envChange); + + responseMessage.Add(envChange); + } + // Create DONE token TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs index d49e10416a..c92182c7aa 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs @@ -75,6 +75,11 @@ public class TDSServerArguments /// public SslProtocols EncryptionProtocols { get; set; } + /// + /// Specifies the failover partner server name and port + /// + public string FailoverPartner { get; set; } = string.Empty; + /// /// Initialization constructor /// From d3e42d62b1d5571a009f1c38d63976e70892fae1 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 22 Oct 2025 14:20:29 -0700 Subject: [PATCH 06/10] Set backing field using reflection. --- .../SqlConnectionBasicTests.cs | 102 ++++++++++-------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index 8715bd138b..51ea720d7b 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -23,10 +23,14 @@ public class SqlConnectionBasicTests { // Reflection public static Assembly systemData = Assembly.GetAssembly(typeof(SqlConnection)); + public static Type sqlConnection = systemData.GetType("Microsoft.Data.SqlClient.SqlConnection"); public static PropertyInfo innerConnectionProperty = sqlConnection.GetProperty("InnerConnection", BindingFlags.NonPublic | BindingFlags.Instance); public static Type sqlInternalConnectionTds = systemData.GetType("Microsoft.Data.SqlClient.SqlInternalConnectionTds"); public static PropertyInfo serverProvidedFailoverPartnerProperty = sqlInternalConnectionTds.GetProperty("ServerProvidedFailoverPartner", BindingFlags.NonPublic | BindingFlags.Instance); + public static Type localAppContextSwitches = systemData.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); + public static FieldInfo ignoreServerProvidedFailoverPartnerField = localAppContextSwitches.GetField("s_ignoreServerProvidedFailoverPartner", BindingFlags.NonPublic | BindingFlags.Static); + [Fact] public void ConnectionTest() @@ -307,57 +311,65 @@ public void ConnectionTestValidCredentialCombination() public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() { // Arrange - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner", true); - - using TestTdsServer failoverServer = TestTdsServer.StartTestServer(); - // Doesn't need to point to a real endpoint, just needs a value specified - failoverServer.Arguments.FailoverPartner = "localhost,1234"; - - var failoverBuilder = new SqlConnectionStringBuilder(failoverServer.ConnectionString); - - using TestTdsServer server = TestTdsServer.StartTestServer(); - // Set an invalid failover partner to ensure that the connection fails if the - // server provided failover partner is used. - server.Arguments.FailoverPartner = $"invalidhost"; + ignoreServerProvidedFailoverPartnerField.SetValue(null, true); - SqlConnectionStringBuilder builder = new(server.ConnectionString) + try { - InitialCatalog = "master", - Encrypt = false, - FailoverPartner = failoverBuilder.DataSource, - // Ensure pooling is enabled so that the failover partner information - // is persisted in the pool group. If pooling is disabled, the server - // provided failover partner will never be used. - Pooling = true, - }; - SqlConnection connection = new(builder.ConnectionString); - - // Connect once to the primary to trigger it to send the failover partner - connection.Open(); + using TestTdsServer failoverServer = TestTdsServer.StartTestServer(); + // Doesn't need to point to a real endpoint, just needs a value specified + failoverServer.Arguments.FailoverPartner = "localhost,1234"; - var innerConnection = innerConnectionProperty.GetValue(connection); - var serverProvidedFailoverPartner = serverProvidedFailoverPartnerProperty.GetValue(innerConnection); - Assert.Equal("invalidhost", serverProvidedFailoverPartner); + var failoverBuilder = new SqlConnectionStringBuilder(failoverServer.ConnectionString); - // Close the connection to return it to the pool - connection.Close(); + using TestTdsServer server = TestTdsServer.StartTestServer(); + // Set an invalid failover partner to ensure that the connection fails if the + // server provided failover partner is used. + server.Arguments.FailoverPartner = $"invalidhost"; - // Act - // Dispose of the server to trigger a failover - server.Dispose(); + SqlConnectionStringBuilder builder = new(server.ConnectionString) + { + InitialCatalog = "master", + Encrypt = false, + FailoverPartner = failoverBuilder.DataSource, + // Ensure pooling is enabled so that the failover partner information + // is persisted in the pool group. If pooling is disabled, the server + // provided failover partner will never be used. + Pooling = true, + ConnectTimeout = 10000 + }; + SqlConnection connection = new(builder.ConnectionString); + + // Connect once to the primary to trigger it to send the failover partner + connection.Open(); - // Opening a new connection will use the failover partner stored in the pool group. - // This will fail if the server provided failover partner was stored to the pool group. - using SqlConnection failoverConnection = new(builder.ConnectionString); - failoverConnection.Open(); - - // Assert - Assert.Equal(ConnectionState.Open, failoverConnection.State); - Assert.Equal(failoverBuilder.DataSource, failoverConnection.DataSource); - // 1 for the initial connection - Assert.Equal(1, server.PreLoginCount); - // 1 for the failover connection - Assert.Equal(1, failoverServer.PreLoginCount); + var innerConnection = innerConnectionProperty.GetValue(connection); + var serverProvidedFailoverPartner = serverProvidedFailoverPartnerProperty.GetValue(innerConnection); + Assert.Equal("invalidhost", serverProvidedFailoverPartner); + + // Close the connection to return it to the pool + connection.Close(); + + // Act + // Dispose of the server to trigger a failover + server.Dispose(); + + // Opening a new connection will use the failover partner stored in the pool group. + // This will fail if the server provided failover partner was stored to the pool group. + using SqlConnection failoverConnection = new(builder.ConnectionString); + failoverConnection.Open(); + + // Assert + Assert.Equal(ConnectionState.Open, failoverConnection.State); + Assert.Equal(failoverBuilder.DataSource, failoverConnection.DataSource); + // 1 for the initial connection + Assert.Equal(1, server.PreLoginCount); + // 1 for the failover connection + Assert.Equal(1, failoverServer.PreLoginCount); + } + finally + { + ignoreServerProvidedFailoverPartnerField.SetValue(null, false); + } } From d895aad99c57d2d7d1b6ed1515dc1bf97bead50a Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 23 Oct 2025 11:40:38 -0700 Subject: [PATCH 07/10] Try clearing pool to make test more reliable. --- .../tests/FunctionalTests/SqlConnectionBasicTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index 51ea720d7b..eb78e61b71 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -312,6 +312,7 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs { // Arrange ignoreServerProvidedFailoverPartnerField.SetValue(null, true); + try { @@ -335,7 +336,7 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // is persisted in the pool group. If pooling is disabled, the server // provided failover partner will never be used. Pooling = true, - ConnectTimeout = 10000 + MinPoolSize = 1, }; SqlConnection connection = new(builder.ConnectionString); @@ -356,6 +357,10 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // Opening a new connection will use the failover partner stored in the pool group. // This will fail if the server provided failover partner was stored to the pool group. using SqlConnection failoverConnection = new(builder.ConnectionString); + + // Clear the pool to ensure a new physical connection is created + // Pool group info such as failover partner will still be retained + SqlConnection.ClearPool(connection); failoverConnection.Open(); // Assert From 1310e3d809662a2d5ef61e92abfe69008408db3b Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 23 Oct 2025 13:42:46 -0700 Subject: [PATCH 08/10] Use string.Empty --- .../src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs | 2 +- .../src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 40a12a686e..72860edf6d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1676,7 +1676,7 @@ private void LoginNoFailover(ServerInfo serverInfo, // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string. // Otherwise the pool group's failover partner designation will be updated to point to the server provided value. - string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? "" : ServerProvidedFailoverPartner; + string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? string.Empty : ServerProvidedFailoverPartner; PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, actualFailoverPartner); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 8f50a63e26..38bf669ded 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1965,7 +1965,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt // When ignoring server provided failover partner, we must pass in the original failover partner from the connection string. // Otherwise the pool group's failover partner designation will be updated to point to the server provided value. - string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? "" : ServerProvidedFailoverPartner; + string actualFailoverPartner = LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner ? string.Empty : ServerProvidedFailoverPartner; PoolGroupProviderInfo.FailoverCheck(false, connectionOptions, actualFailoverPartner); } From b1403391adf0b397d721732552d74a4a5dbd0b93 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 23 Oct 2025 14:07:12 -0700 Subject: [PATCH 09/10] Review changes --- .../src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs | 2 +- .../src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs | 2 +- .../tests/FunctionalTests/SqlConnectionBasicTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 72860edf6d..933a4a3691 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -675,7 +675,7 @@ internal TdsParser Parser } } - internal string ServerProvidedFailoverPartner { get; set; } + internal string ServerProvidedFailoverPartner { get; private set; } internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 38bf669ded..2d6c18134b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -770,7 +770,7 @@ internal TdsParser Parser } } - internal string ServerProvidedFailoverPartner { get; set; } + internal string ServerProvidedFailoverPartner { get; private set; } internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index eb78e61b71..91f773a684 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -311,7 +311,7 @@ public void ConnectionTestValidCredentialCombination() public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() { // Arrange - ignoreServerProvidedFailoverPartnerField.SetValue(null, true); + ignoreServerProvidedFailoverPartnerField.SetValue(null, ); try From cccd71cdd5f27d26b780364d6b8431b3aca222df Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 23 Oct 2025 14:14:58 -0700 Subject: [PATCH 10/10] Update app context switch reflection. --- .../tests/FunctionalTests/SqlConnectionBasicTests.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index 91f773a684..3ef7287c00 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -30,6 +30,9 @@ public class SqlConnectionBasicTests public static PropertyInfo serverProvidedFailoverPartnerProperty = sqlInternalConnectionTds.GetProperty("ServerProvidedFailoverPartner", BindingFlags.NonPublic | BindingFlags.Instance); public static Type localAppContextSwitches = systemData.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); public static FieldInfo ignoreServerProvidedFailoverPartnerField = localAppContextSwitches.GetField("s_ignoreServerProvidedFailoverPartner", BindingFlags.NonPublic | BindingFlags.Static); + public static Type tristateEnum = localAppContextSwitches.GetNestedType("Tristate", BindingFlags.NonPublic); + public static object tristateTrue = Enum.Parse(tristateEnum, "True"); + public static object tristateFalse = Enum.Parse(tristateEnum, "False"); [Fact] @@ -311,7 +314,7 @@ public void ConnectionTestValidCredentialCombination() public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() { // Arrange - ignoreServerProvidedFailoverPartnerField.SetValue(null, ); + ignoreServerProvidedFailoverPartnerField.SetValue(null, tristateTrue); try @@ -373,7 +376,7 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs } finally { - ignoreServerProvidedFailoverPartnerField.SetValue(null, false); + ignoreServerProvidedFailoverPartnerField.SetValue(null, tristateFalse); } }