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 ca5ebb2226..793c981a7e 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 702be5f842..ecba075025 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
@@ -22,6 +22,7 @@ private enum Tristate : byte
internal const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour";
internal const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2";
private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner";
+ private const string EnableMultiSubnetFailoverByDefaultString = @"Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault";
// 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;
@@ -32,6 +33,7 @@ private enum Tristate : byte
private static Tristate s_legacyVarTimeZeroScaleBehaviour;
private static Tristate s_useConnectionPoolV2;
private static Tristate s_ignoreServerProvidedFailoverPartner;
+ private static Tristate s_multiSubnetFailoverByDefault;
#if NET
@@ -263,5 +265,30 @@ public static bool IgnoreServerProvidedFailoverPartner
return s_ignoreServerProvidedFailoverPartner == Tristate.True;
}
}
+
+ ///
+ /// 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/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
index b96a946ac7..67adcf0674 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
@@ -45,7 +45,7 @@ internal static class DEFAULT
internal const bool MARS = DbConnectionStringDefaults.MultipleActiveResultSets;
internal const int Max_Pool_Size = DbConnectionStringDefaults.MaxPoolSize;
internal const int Min_Pool_Size = DbConnectionStringDefaults.MinPoolSize;
- internal const bool MultiSubnetFailover = DbConnectionStringDefaults.MultiSubnetFailover;
+ internal static bool MultiSubnetFailover => DbConnectionStringDefaults.MultiSubnetFailover;
internal const int Packet_Size = DbConnectionStringDefaults.PacketSize;
internal const string Password = DbConnectionStringDefaults.Password;
internal const bool Persist_Security_Info = DbConnectionStringDefaults.PersistSecurityInfo;
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs
index 734a4377fa..e28b9e288e 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs
@@ -32,6 +32,7 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable
#if NETFRAMEWORK
private readonly PropertyInfo _disableTnirByDefaultProperty;
#endif
+ private readonly PropertyInfo _enableMultiSubnetFailoverByDefaultProperty;
// These fields are used to capture the original switch values.
private readonly FieldInfo _legacyRowVersionNullBehaviorField;
@@ -48,10 +49,13 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable
private readonly Tristate _useConnectionPoolV2Original;
private readonly FieldInfo _ignoreServerProvidedFailoverPartnerField;
private readonly Tristate _ignoreServerProvidedFailoverPartnerOriginal;
-#if NETFRAMEWORK
+ #if NETFRAMEWORK
private readonly FieldInfo _disableTnirByDefaultField;
private readonly Tristate _disableTnirByDefaultOriginal;
-#endif
+ #endif
+ private readonly FieldInfo _multiSubnetFailoverByDefaultField;
+ private readonly Tristate _multiSubnetFailoverByDefaultOriginal;
+
#endregion
@@ -139,6 +143,10 @@ void InitProperty(string name, out PropertyInfo property)
out _disableTnirByDefaultProperty);
#endif
+ InitProperty(
+ "EnableMultiSubnetFailoverByDefault",
+ out _enableMultiSubnetFailoverByDefaultProperty);
+
// A local helper to capture the original value of a switch.
void InitField(string name, out FieldInfo field, out Tristate value)
{
@@ -195,6 +203,11 @@ void InitField(string name, out FieldInfo field, out Tristate value)
out _disableTnirByDefaultField,
out _disableTnirByDefaultOriginal);
#endif
+
+ InitField(
+ "s_multiSubnetFailoverByDefault",
+ out _multiSubnetFailoverByDefaultField,
+ out _multiSubnetFailoverByDefaultOriginal);
}
///
@@ -255,6 +268,10 @@ void RestoreField(FieldInfo field, Tristate value)
_disableTnirByDefaultOriginal);
#endif
+ RestoreField(
+ _multiSubnetFailoverByDefaultField,
+ _multiSubnetFailoverByDefaultOriginal);
+
if (failedFields.Count > 0)
{
throw new Exception(
@@ -327,6 +344,11 @@ public bool DisableTnirByDefault
}
#endif
+ public bool EnableMultiSubnetFailoverByDefault
+ {
+ get => (bool)_enableMultiSubnetFailoverByDefaultProperty.GetValue(null);
+ }
+
// These properties get or set the like-named underlying switch field value.
//
// They all fail the test if the value cannot be retrieved or set.
@@ -408,6 +430,12 @@ public Tristate DisableTnirByDefaultField
}
#endif
+ public Tristate EnableMultiSubnetFailoverByDefaultField
+ {
+ get => GetValue(_multiSubnetFailoverByDefaultField);
+ set => SetValue(_multiSubnetFailoverByDefaultField, value);
+ }
+
#endregion
#region Private Helpers
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs
index 30896e545e..bd4dcb2b0c 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs
@@ -20,6 +20,7 @@ public class LocalAppContextSwitchesTests
#if NETFRAMEWORK
[InlineData("DisableTnirByDefault", false)]
#endif
+ [InlineData("EnableMultiSubnetFailoverByDefault", false)]
public void DefaultSwitchValue(string property, bool expectedDefaultValue)
{
var switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches");
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 52bc386875..601201d197 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
@@ -17,7 +17,7 @@ public SqlConnectionStringTest()
_appContextSwitchHelper = new LocalAppContextSwitchesHelper();
}
-#if NETFRAMEWORK
+ #if NETFRAMEWORK
[Theory]
[InlineData("test.database.windows.net", true, Tristate.True, true)]
[InlineData("test.database.windows.net", false, Tristate.True, false)]
@@ -60,7 +60,50 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, Tr
// Assert
Assert.Equal(expectedValue, connectionString.TransparentNetworkIPResolution);
}
-#endif
+ #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()
{