diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs index 757b3522fc..460fa0c2cc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs @@ -39,7 +39,7 @@ internal static class DbConnectionStringDefaults internal const int MaxPoolSize = 100; internal const int MinPoolSize = 0; internal const bool MultipleActiveResultSets = false; - internal const bool MultiSubnetFailover = false; + internal static bool MultiSubnetFailover => LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault; internal const int PacketSize = 8000; internal const string Password = ""; internal const bool PersistSecurityInfo = false; 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 a12587411c..09d84e0ea7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -27,6 +27,7 @@ private enum Tristate : byte private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; private const string EnableUserAgentString = @"Switch.Microsoft.Data.SqlClient.EnableUserAgent"; + private const string EnableMultiSubnetFailoverByDefaultString = @"Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault"; #if NET private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; @@ -52,6 +53,7 @@ private enum Tristate : byte private static Tristate s_truncateScaledDecimal; private static Tristate s_ignoreServerProvidedFailoverPartner; private static Tristate s_enableUserAgent; + private static Tristate s_multiSubnetFailoverByDefault; #if NET private static Tristate s_globalizationInvariantMode; @@ -511,6 +513,31 @@ public static bool DisableTnirByDefault return s_disableTnirByDefault == Tristate.True; } } - #endif +#endif + + /// + /// When set to true, the default value for MultiSubnetFailover connection string property + /// will be true instead of false. This enables parallel IP connection attempts for + /// improved connection times in multi-subnet environments. + /// This app context switch defaults to 'false'. + /// + public static bool EnableMultiSubnetFailoverByDefault + { + get + { + if (s_multiSubnetFailoverByDefault == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(EnableMultiSubnetFailoverByDefaultString, out bool returnedValue) && returnedValue) + { + s_multiSubnetFailoverByDefault = Tristate.True; + } + else + { + s_multiSubnetFailoverByDefault = Tristate.False; + } + } + return s_multiSubnetFailoverByDefault == Tristate.True; + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 1798e1fcf4..7d90df85fe 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -33,8 +33,8 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly PropertyInfo _truncateScaledDecimalProperty; private readonly PropertyInfo _ignoreServerProvidedFailoverPartner; private readonly PropertyInfo _enableUserAgent; - - #if NET + private readonly PropertyInfo _enableMultiSubnetFailoverByDefaultProperty; +#if NET private readonly PropertyInfo _globalizationInvariantModeProperty; #endif @@ -69,8 +69,9 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly Tristate _ignoreServerProvidedFailoverPartnerOriginal; private readonly FieldInfo _enableUserAgentField; private readonly Tristate _enableUserAgentOriginal; - - #if NET + private readonly FieldInfo _multiSubnetFailoverByDefaultField; + private readonly Tristate _multiSubnetFailoverByDefaultOriginal; +#if NET private readonly FieldInfo _globalizationInvariantModeField; private readonly Tristate _globalizationInvariantModeOriginal; #endif @@ -181,7 +182,11 @@ void InitProperty(string name, out PropertyInfo property) "EnableUserAgent", out _enableUserAgent); - #if NET + InitProperty( + "EnableMultiSubnetFailoverByDefault", + out _enableMultiSubnetFailoverByDefaultProperty); + +#if NET InitProperty( "GlobalizationInvariantMode", out _globalizationInvariantModeProperty); @@ -269,7 +274,12 @@ void InitField(string name, out FieldInfo field, out Tristate value) out _enableUserAgentField, out _enableUserAgentOriginal); - #if NET + InitField( + "s_multiSubnetFailoverByDefault", + out _multiSubnetFailoverByDefaultField, + out _multiSubnetFailoverByDefaultOriginal); + +#if NET InitField( "s_globalizationInvariantMode", out _globalizationInvariantModeField, @@ -281,8 +291,8 @@ void InitField(string name, out FieldInfo field, out Tristate value) "s_useManagedNetworking", out _useManagedNetworkingField, out _useManagedNetworkingOriginal); - #endif - +#endif + #if NETFRAMEWORK InitField( "s_disableTnirByDefault", @@ -359,6 +369,10 @@ void RestoreField(FieldInfo field, Tristate value) _enableUserAgentField, _enableUserAgentOriginal); + RestoreField( + _multiSubnetFailoverByDefaultField, + _multiSubnetFailoverByDefaultOriginal); + #if NET RestoreField( _globalizationInvariantModeField, @@ -474,6 +488,11 @@ public bool EnableUserAgent get => (bool)_enableUserAgent.GetValue(null); } + public bool EnableMultiSubnetFailoverByDefault + { + get => (bool)_enableMultiSubnetFailoverByDefaultProperty.GetValue(null); + } + #if NET /// /// Access the LocalAppContextSwitches.GlobalizationInvariantMode property. @@ -608,7 +627,13 @@ public Tristate EnableUserAgentField set => SetValue(_enableUserAgentField, value); } - #if NET + public Tristate EnableMultiSubnetFailoverByDefaultField + { + get => GetValue(_multiSubnetFailoverByDefaultField); + set => SetValue(_multiSubnetFailoverByDefaultField, value); + } + +#if NET /// /// Get or set the LocalAppContextSwitches.GlobalizationInvariantMode switch value. /// diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index c28fe18978..c92402af41 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -27,6 +27,7 @@ public void TestDefaultAppContextSwitchValues() Assert.True(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); Assert.False(LocalAppContextSwitches.TruncateScaledDecimal); + Assert.False(LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault); #if NETFRAMEWORK Assert.False(LocalAppContextSwitches.DisableTnirByDefault); Assert.False(LocalAppContextSwitches.UseManagedNetworking); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs index 14bc51d520..e413821932 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs @@ -58,6 +58,48 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, Tr Assert.Equal(expectedValue, connectionString.TransparentNetworkIPResolution); } #endif + /// + /// Test MSF values when set through connection string and through app context switch. + /// + [Theory] + [InlineData(true, Tristate.True, true)] + [InlineData(false, Tristate.True, false)] + [InlineData(null, Tristate.True, true)] + [InlineData(true, Tristate.False, true)] + [InlineData(false, Tristate.False, false)] + [InlineData(null, Tristate.False, false)] + [InlineData(null, Tristate.NotInitialized, false)] + public void TestDefaultMultiSubnetFailover(bool? msfInConnString, Tristate msfEnabledAppContext, bool expectedValue) + { + _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultField = msfEnabledAppContext; + + SqlConnectionStringBuilder builder = new(); + if (msfInConnString.HasValue) + { + builder.MultiSubnetFailover = msfInConnString.Value; + } + SqlConnectionString connectionString = new(builder.ConnectionString); + + Assert.Equal(expectedValue, connectionString.MultiSubnetFailover); + } + + /// + /// Tests that MultiSubnetFailover=true cannot be used with FailoverPartner. + /// + [Fact] + public void TestMultiSubnetFailoverWithFailoverPartnerThrows() + { + _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultField = Tristate.True; + + SqlConnectionStringBuilder builder = new() + { + DataSource = "server", + FailoverPartner = "partner", + InitialCatalog = "database" + }; + + Assert.Throws(() => new SqlConnectionString(builder.ConnectionString)); + } public void Dispose() {