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 09d84e0ea7..74fd226728 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
@@ -4,540 +4,673 @@
using System;
-namespace Microsoft.Data.SqlClient
+#nullable enable
+
+namespace Microsoft.Data.SqlClient;
+
+///
+/// This class provides immutable access to the app context switches used
+/// throughout the codebase. Each switch value is read once (on first access)
+/// and cached for future access. Most switch values are obtained solely from
+/// the AppContext, but some switches may use other mechanisms to determine
+/// their values as well (e.g. environment variables).
+///
+internal static class LocalAppContextSwitches
{
- internal static partial class LocalAppContextSwitches
+ #region Switch Names
+
+ #if NETFRAMEWORK
+ ///
+ /// The name of the app context switch that controls whether TNIR is
+ /// disabled by default in the connection string.
+ ///
+ private const string DisableTnirByDefaultString =
+ "Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString";
+ #endif
+
+ ///
+ /// The name of the app context switch that controls whether
+ /// MultiSubnetFailover is enabled by default in the connection string.
+ ///
+ private const string EnableMultiSubnetFailoverByDefaultString =
+ "Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault";
+
+ ///
+ /// The name of the app context switch that controls whether
+ /// the user agent feature is enabled.
+ ///
+ private const string EnableUserAgentString =
+ "Switch.Microsoft.Data.SqlClient.EnableUserAgent";
+
+ #if NET
+ ///
+ /// The name of the app context switch that controls whether
+ /// Globalization Invariant mode is enabled.
+ ///
+ private const string GlobalizationInvariantModeString =
+ "System.Globalization.Invariant";
+ ///
+ /// The name of the environment variable that controls whether
+ /// Globalization Invariant mode is enabled.
+ ///
+ private const string GlobalizationInvariantModeEnvironmentVariable =
+ "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT";
+ #endif
+
+ ///
+ /// The name of the app context switch that controls whether the failover
+ /// partner provided by the server during connection will be ignored.
+ ///
+ private const string IgnoreServerProvidedFailoverPartnerString =
+ "Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner";
+
+ ///
+ /// The name of the app context switch that controls whether to preserve
+ /// legacy behavior where Timestamp/RowVersion fields return empty byte
+ /// arrays instead of null.
+ ///
+ private const string LegacyRowVersionNullString =
+ "Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior";
+
+ ///
+ /// The name of the app context switch that controls whether to preserve
+ /// legacy VarTime zero scale behavior for datetime2, datetimeoffset, and
+ /// time data types.
+ ///
+ private const string LegacyVarTimeZeroScaleBehaviourString =
+ "Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour";
+
+ ///
+ /// The name of the app context switch that controls whether
+ /// ReadAsync operations run synchronously and block the calling thread.
+ ///
+ private const string MakeReadAsyncBlockingString =
+ "Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking";
+
+ ///
+ /// The name of the app context switch that controls whether to suppress
+ /// the security warning when using Encrypt=false with TLS 1.2 or lower.
+ ///
+ private const string SuppressInsecureTlsWarningString =
+ "Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning";
+
+ ///
+ /// The name of the app context switch that controls whether TdsParser
+ /// truncates (rather than rounds) decimal and SqlDecimal values when
+ /// scaling them.
+ ///
+ private const string TruncateScaledDecimalString =
+ "Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal";
+
+ ///
+ /// The name of the app context switch that controls whether to use legacy
+ /// async multi-packet column value fetch behavior without continue snapshot
+ /// state.
+ ///
+ private const string UseCompatibilityAsyncBehaviourString =
+ "Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour";
+
+ ///
+ /// The name of the app context switch that controls whether to use the old
+ /// ProcessSni design instead of the packet multiplexing implementation.
+ ///
+ private const string UseCompatibilityProcessSniString =
+ "Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni";
+
+ ///
+ /// The name of the app context switch that controls whether to use the new
+ /// V2 connection pool implementation or the legacy V1 implementation.
+ ///
+ private const string UseConnectionPoolV2String =
+ "Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2";
+
+ #if NET && _WINDOWS
+ ///
+ /// The name of the app context switch that controls whether to use the
+ /// managed SNI implementation instead of the native SNI implementation on
+ /// Windows.
+ ///
+ private const string UseManagedNetworkingOnWindowsString =
+ "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows";
+ #endif
+
+ ///
+ /// The name of the app context switch that controls whether to enforce
+ /// a minimum login timeout of 1 second instead of 0 seconds.
+ ///
+ private const string UseMinimumLoginTimeoutString =
+ "Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin";
+
+ #endregion
+
+ #region Switch Values
+
+ ///
+ /// We use a byte-based enum to track the value of each switch. This plays
+ /// nicely with threaded access. A nullable bool would seem to be the
+ /// obvious choice, but the way nullable bools are implemented in the CLR
+ /// makes them not thread-safe without using locks (the HasValue and Value
+ /// properties can get out of sync if one thread is writing while another is
+ /// reading).
+ ///
+ private enum SwitchValue : byte
{
- // @TODO: Replace with `bool?` since that's exactly how this is being used
- private enum Tristate : byte
- {
- NotInitialized = 0,
- False = 1,
- True = 2
- }
+ None = 0,
+ True = 1,
+ False = 2
+ }
- 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";
- 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";
- private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT";
-
- #if _WINDOWS
- private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows";
- #endif
- #else
- private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString";
- #endif
-
- // 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;
- private static Tristate s_suppressInsecureTlsWarning;
- private static Tristate s_makeReadAsyncBlocking;
- 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;
- 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;
- private static Tristate s_enableUserAgent;
- private static Tristate s_multiSubnetFailoverByDefault;
-
- #if NET
- private static Tristate s_globalizationInvariantMode;
-
- #if _WINDOWS
- private static Tristate s_useManagedNetworking;
- #endif
- #else
- private static Tristate s_disableTnirByDefault;
- #endif
-
- #if NET
- static LocalAppContextSwitches()
- {
- IAppContextSwitchOverridesSection appContextSwitch = AppConfigManager.FetchConfigurationSection(AppContextSwitchOverridesSection.Name);
+ // GOTCHA: These fields are accessed via reflection by the
+ // LocalAppContextSwitchesHelper test helper class. If you rename them, be
+ // sure to update the test helper as well.
+
+ #if NETFRAMEWORK
+ ///
+ /// The cached value of the DisableTnirByDefault switch.
+ ///
+ private static SwitchValue s_disableTnirByDefault = SwitchValue.None;
+ #endif
- try
- {
- SqlAppContextSwitchManager.ApplyContextSwitches(appContextSwitch);
- }
- catch (Exception e)
- {
- // @TODO: Adopt netcore style of trace logs
- // Don't throw an exception for an invalid config file
- SqlClientEventSource.Log.TryTraceEvent(": {1}", nameof(LocalAppContextSwitches), e);
- }
- }
- #endif
-
- // @TODO: Sort by name
-
- ///
- /// In TdsParser, the ProcessSni function changed significantly when the packet
- /// multiplexing code needed for high speed multi-packet column values was added.
- /// When this switch is set to true (the default), the old ProcessSni design is used.
- /// When this switch is set to false, the new experimental ProcessSni behavior using
- /// the packet multiplexer is enabled.
- ///
- public static bool UseCompatibilityProcessSni
- {
- get
- {
- if (s_useCompatibilityProcessSni == Tristate.NotInitialized)
- {
- // Check if the switch has been set by the AppContext switch directly
- // If it has not been set, we default to true.
- if (!AppContext.TryGetSwitch(UseCompatibilityProcessSniString, out bool returnedValue) || returnedValue)
- {
- s_useCompatibilityProcessSni = Tristate.True;
- }
- else
- {
- s_useCompatibilityProcessSni = Tristate.False;
- }
- }
- return s_useCompatibilityProcessSni == Tristate.True;
- }
- }
+ ///
+ /// The cached value of the EnableMultiSubnetFailoverByDefault switch.
+ ///
+ private static SwitchValue s_enableMultiSubnetFailoverByDefault = SwitchValue.None;
- ///
- /// In TdsParser, the async multi-packet column value fetch behavior can use a continue snapshot state
- /// for improved efficiency. When this switch is enabled (the default), the driver preserves the legacy
- /// compatibility behavior, which does not use the continue snapshot state. When disabled, the new behavior
- /// using the continue snapshot state is enabled. This switch will always return true if
- /// is enabled, because the continue state is not stable without
- /// the multiplexer.
- ///
- public static bool UseCompatibilityAsyncBehaviour
- {
- get
- {
- if (UseCompatibilityProcessSni)
- {
- // If ProcessSni compatibility mode has been enabled then the packet
- // multiplexer has been disabled. The new async behaviour using continue
- // point capture is only stable if the multiplexer is enabled so we must
- // return true to enable compatibility async behaviour using only restarts.
- return true;
- }
-
- if (s_useCompatibilityAsyncBehaviour == Tristate.NotInitialized)
- {
- if (!AppContext.TryGetSwitch(UseCompatibilityAsyncBehaviourString, out bool returnedValue) || returnedValue)
- {
- s_useCompatibilityAsyncBehaviour = Tristate.True;
- }
- else
- {
- s_useCompatibilityAsyncBehaviour = Tristate.False;
- }
- }
- return s_useCompatibilityAsyncBehaviour == Tristate.True;
- }
- }
+ ///
+ /// The cached value of the EnableUserAgent switch.
+ ///
+ private static SwitchValue s_enableUserAgent = SwitchValue.None;
+
+ #if NET
+ ///
+ /// The cached value of the GlobalizationInvariantMode switch.
+ ///
+ private static SwitchValue s_globalizationInvariantMode = SwitchValue.None;
+ #endif
+
+ ///
+ /// The cached value of the IgnoreServerProvidedFailoverPartner switch.
+ ///
+ private static SwitchValue s_ignoreServerProvidedFailoverPartner = SwitchValue.None;
+
+ ///
+ /// The cached value of the LegacyRowVersionNullBehavior switch.
+ ///
+ private static SwitchValue s_legacyRowVersionNullBehavior = SwitchValue.None;
+
+ ///
+ /// The cached value of the LegacyVarTimeZeroScaleBehaviour switch.
+ ///
+ private static SwitchValue s_legacyVarTimeZeroScaleBehaviour = SwitchValue.None;
+
+ ///
+ /// The cached value of the MakeReadAsyncBlocking switch.
+ ///
+ private static SwitchValue s_makeReadAsyncBlocking = SwitchValue.None;
- ///
- /// When using Encrypt=false in the connection string, a security warning is output to the console if the TLS version is 1.2 or lower.
- /// This warning can be suppressed by enabling this AppContext switch.
- /// This app context switch defaults to 'false'.
- ///
- public static bool SuppressInsecureTlsWarning
+ ///
+ /// The cached value of the SuppressInsecureTlsWarning switch.
+ ///
+ private static SwitchValue s_suppressInsecureTlsWarning = SwitchValue.None;
+
+ ///
+ /// The cached value of the TruncateScaledDecimal switch.
+ ///
+ private static SwitchValue s_truncateScaledDecimal = SwitchValue.None;
+
+ ///
+ /// The cached value of the UseCompatibilityAsyncBehaviour switch.
+ ///
+ private static SwitchValue s_useCompatibilityAsyncBehaviour = SwitchValue.None;
+
+ ///
+ /// The cached value of the UseCompatibilityProcessSni switch.
+ ///
+ private static SwitchValue s_useCompatibilityProcessSni = SwitchValue.None;
+
+ ///
+ /// The cached value of the UseConnectionPoolV2 switch.
+ ///
+ private static SwitchValue s_useConnectionPoolV2 = SwitchValue.None;
+
+ #if NET && _WINDOWS
+ ///
+ /// The cached value of the UseManagedNetworking switch.
+ ///
+ private static SwitchValue s_useManagedNetworking = SwitchValue.None;
+ #endif
+
+ ///
+ /// The cached value of the UseMinimumLoginTimeout switch.
+ ///
+ private static SwitchValue s_useMinimumLoginTimeout = SwitchValue.None;
+
+ #endregion
+
+ #region Static Initialization
+
+ #if NET
+ ///
+ /// Static construction for .NET reads the AppContextSwitchOverridesSection
+ /// from the default app config file and applies any switch values found
+ /// there to the AppContext. These values are then later read and cached by
+ /// the individual switch properties on first access.
+ ///
+ static LocalAppContextSwitches()
+ {
+ IAppContextSwitchOverridesSection appContextSwitch = AppConfigManager.FetchConfigurationSection(AppContextSwitchOverridesSection.Name);
+
+ try
{
- get
- {
- if (s_suppressInsecureTlsWarning == Tristate.NotInitialized)
- {
- if (AppContext.TryGetSwitch(SuppressInsecureTlsWarningString, out bool returnedValue) && returnedValue)
- {
- s_suppressInsecureTlsWarning = Tristate.True;
- }
- else
- {
- s_suppressInsecureTlsWarning = Tristate.False;
- }
- }
- return s_suppressInsecureTlsWarning == Tristate.True;
- }
+ SqlAppContextSwitchManager.ApplyContextSwitches(appContextSwitch);
}
-
- ///
- /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a field with type Timestamp/RowVersion
- /// would return an empty byte array. This switch controls whether to preserve that behaviour on newer versions
- /// of Microsoft.Data.SqlClient, if this switch returns false an appropriate null value will be returned.
- /// This app context switch defaults to 'false'.
- ///
- public static bool LegacyRowVersionNullBehavior
+ catch (Exception e)
{
- get
- {
- if (s_legacyRowVersionNullBehavior == Tristate.NotInitialized)
- {
- if (AppContext.TryGetSwitch(LegacyRowVersionNullString, out bool returnedValue) && returnedValue)
- {
- s_legacyRowVersionNullBehavior = Tristate.True;
- }
- else
- {
- s_legacyRowVersionNullBehavior = Tristate.False;
- }
- }
- return s_legacyRowVersionNullBehavior == Tristate.True;
- }
+ // @TODO: Adopt netcore style of trace logs
+ // Don't throw an exception for an invalid config file
+ SqlClientEventSource.Log.TryTraceEvent(": {1}", nameof(LocalAppContextSwitches), e);
}
+ }
+ #endif
+
+ #endregion
+
+ #region Switch Properties
+
+ #if NETFRAMEWORK
+ ///
+ /// 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.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool DisableTnirByDefault =>
+ AcquireAndReturn(
+ DisableTnirByDefaultString,
+ defaultValue: false,
+ ref s_disableTnirByDefault);
+ #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.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool EnableMultiSubnetFailoverByDefault =>
+ AcquireAndReturn(
+ EnableMultiSubnetFailoverByDefaultString,
+ defaultValue: false,
+ ref s_enableMultiSubnetFailoverByDefault);
- ///
- /// When enabled, ReadAsync runs asynchronously and does not block the calling thread.
- /// This app context switch defaults to 'false'.
- ///
- public static bool MakeReadAsyncBlocking
+ ///
+ /// When set to true, the user agent feature is enabled and the driver will
+ /// send the user agent string to the server.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool EnableUserAgent =>
+ AcquireAndReturn(
+ EnableUserAgentString,
+ defaultValue: false,
+ ref s_enableUserAgent);
+
+ #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.
+ ///
+ /// The value of this switch is determined first by checking the AppContext.
+ /// If not set there, it falls back to checking the environment variable
+ /// DOTNET_SYSTEM_GLOBALIZATION_INVARIANT. If neither is set, it attempts to
+ /// create the "en-US" culture and infers invariant mode from whether that
+ /// operation throws an exception.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool GlobalizationInvariantMode
+ {
+ get
{
- get
+ if (s_globalizationInvariantMode != SwitchValue.None)
{
- if (s_makeReadAsyncBlocking == Tristate.NotInitialized)
- {
- if (AppContext.TryGetSwitch(MakeReadAsyncBlockingString, out bool returnedValue) && returnedValue)
- {
- s_makeReadAsyncBlocking = Tristate.True;
- }
- else
- {
- s_makeReadAsyncBlocking = Tristate.False;
- }
- }
- return s_makeReadAsyncBlocking == Tristate.True;
+ return s_globalizationInvariantMode == SwitchValue.True;
}
- }
- ///
- /// Specifies minimum login timeout to be set to 1 second instead of 0 seconds,
- /// to prevent a login attempt from waiting indefinitely.
- /// This app context switch defaults to 'true'.
- ///
- public static bool UseMinimumLoginTimeout
- {
- get
+ // Check if invariant mode has been set by the AppContext switch directly
+ if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue)
{
- if (s_useMinimumLoginTimeout == Tristate.NotInitialized)
- {
- if (!AppContext.TryGetSwitch(UseMinimumLoginTimeoutString, out bool returnedValue) || returnedValue)
- {
- s_useMinimumLoginTimeout = Tristate.True;
- }
- else
- {
- s_useMinimumLoginTimeout = Tristate.False;
- }
- }
- return s_useMinimumLoginTimeout == Tristate.True;
+ s_globalizationInvariantMode = SwitchValue.True;
+ return true;
}
- }
+ // TODO(https://github.com/dotnet/SqlClient/pull/3853):
+ //
+ // The intention of the comment below doesn't match the code.
+ //
+ // The comment claims to fallback to the environment variable if the
+ // switch is not set. However, it actually falls-back if the switch
+ // is not set _OR_ it is set to false.
+ //
+ // Should we update the comment or fix the code to match?
- ///
- /// 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)
- /// regardless of switch value.
- /// This app context switch defaults to 'true'.
- ///
- public static bool LegacyVarTimeZeroScaleBehaviour
- {
- get
+ // 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))
{
- if (s_legacyVarTimeZeroScaleBehaviour == Tristate.NotInitialized)
- {
- if (!AppContext.TryGetSwitch(LegacyVarTimeZeroScaleBehaviourString, out bool returnedValue))
- {
- s_legacyVarTimeZeroScaleBehaviour = Tristate.True;
- }
- else
- {
- s_legacyVarTimeZeroScaleBehaviour = returnedValue ? Tristate.True : Tristate.False;
- }
- }
- return s_legacyVarTimeZeroScaleBehaviour == Tristate.True;
+ s_globalizationInvariantMode = SwitchValue.True;
+ return true;
}
- }
- ///
- /// 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
+ // TODO(https://github.com/dotnet/SqlClient/pull/3853):
+ //
+ // What if the environment variable is set to false? Why are we
+ // ignoring that case?
+
+ // 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
{
- 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;
+ s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant")
+ ? SwitchValue.True
+ : SwitchValue.False;
}
- }
-
- ///
- /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them.
- ///
- public static bool TruncateScaledDecimal
- {
- get
+ catch (System.Globalization.CultureNotFoundException)
{
- 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;
+ // If the culture is not found, it means we are in invariant mode
+ s_globalizationInvariantMode = SwitchValue.True;
}
+
+ return s_globalizationInvariantMode == SwitchValue.True;
}
+ }
+ #else
+ ///
+ /// .NET Framework does not support Globalization Invariant mode, so this
+ /// will always be false.
+ ///
+ public static bool GlobalizationInvariantMode => false;
+ #endif
+
+ ///
+ /// 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.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool IgnoreServerProvidedFailoverPartner =>
+ AcquireAndReturn(
+ IgnoreServerProvidedFailoverPartnerString,
+ defaultValue: false,
+ ref s_ignoreServerProvidedFailoverPartner);
+
+ ///
+ /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a
+ /// field with type Timestamp/RowVersion would return an empty byte array.
+ /// This switch controls whether to preserve that behaviour on newer
+ /// versions of Microsoft.Data.SqlClient, if this switch returns false an
+ /// appropriate null value will be returned.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool LegacyRowVersionNullBehavior =>
+ AcquireAndReturn(
+ LegacyRowVersionNullString,
+ defaultValue: false,
+ ref s_legacyRowVersionNullBehavior);
+
+ ///
+ /// 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) regardless of switch value.
+ ///
+ /// The default value of this switch is true.
+ ///
+ public static bool LegacyVarTimeZeroScaleBehaviour =>
+ AcquireAndReturn(
+ LegacyVarTimeZeroScaleBehaviourString,
+ defaultValue: true,
+ ref s_legacyVarTimeZeroScaleBehaviour);
+
+ ///
+ /// When enabled, ReadAsync runs asynchronously and does not block the
+ /// calling thread.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool MakeReadAsyncBlocking =>
+ AcquireAndReturn(
+ MakeReadAsyncBlockingString,
+ defaultValue: false,
+ ref s_makeReadAsyncBlocking);
+
+ ///
+ /// When using Encrypt=false in the connection string, a security warning is
+ /// output to the console if the TLS version is 1.2 or lower. This warning
+ /// can be suppressed by enabling this AppContext switch.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool SuppressInsecureTlsWarning =>
+ AcquireAndReturn(
+ SuppressInsecureTlsWarningString,
+ defaultValue: false,
+ ref s_suppressInsecureTlsWarning);
- ///
- /// 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
+ ///
+ /// When set to true, TdsParser will truncate (rather than round) decimal
+ /// and SqlDecimal values when scaling them.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool TruncateScaledDecimal =>
+ AcquireAndReturn(
+ TruncateScaledDecimalString,
+ defaultValue: false,
+ ref s_truncateScaledDecimal);
+
+ ///
+ /// In TdsParser, the async multi-packet column value fetch behavior can use
+ /// a continue snapshot state for improved efficiency. When this switch is
+ /// enabled (the default), the driver preserves the legacy compatibility
+ /// behavior, which does not use the continue snapshot state. When disabled,
+ /// the new behavior using the continue snapshot state is enabled.
+ ///
+ /// This switch will always return true if
+ /// is enabled, because the
+ /// continue state is not stable without the multiplexer.
+ ///
+ /// The default value of this switch is true.
+ ///
+ public static bool UseCompatibilityAsyncBehaviour
+ {
+ get
{
- get
+ if (UseCompatibilityProcessSni)
{
- 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 ProcessSni compatibility mode has been enabled then the
+ // packet multiplexer has been disabled. The new async behaviour
+ // using continue point capture is only stable if the
+ // multiplexer is enabled so we must return true to enable
+ // compatibility async behaviour using only restarts.
+ return true;
}
+
+ return AcquireAndReturn(
+ UseCompatibilityAsyncBehaviourString,
+ defaultValue: true,
+ ref s_useCompatibilityAsyncBehaviour);
}
- ///
- /// When set to true, the user agent feature is enabled and the driver will send the user agent string to the server.
- ///
- public static bool EnableUserAgent
+ }
+
+ ///
+ /// In TdsParser, the ProcessSni function changed significantly when the
+ /// packet multiplexing code needed for high speed multi-packet column
+ /// values was added. When this switch is set to true (the default), the
+ /// old ProcessSni design is used. When this switch is set to false, the
+ /// new experimental ProcessSni behavior using the packet multiplexer is
+ /// enabled.
+ ///
+ /// The default value of this switch is true.
+ ///
+ public static bool UseCompatibilityProcessSni =>
+ AcquireAndReturn(
+ UseCompatibilityProcessSniString,
+ defaultValue: true,
+ ref s_useCompatibilityProcessSni);
+
+ ///
+ /// 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.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool UseConnectionPoolV2 =>
+ AcquireAndReturn(
+ UseConnectionPoolV2String,
+ defaultValue: false,
+ ref s_useConnectionPoolV2);
+
+ #if NET && _WINDOWS
+ ///
+ /// When set to true, .NET on Windows will use the managed SNI
+ /// implementation instead of the native SNI implementation.
+ ///
+ /// 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.
+ ///
+ /// The default value of this switch is false.
+ ///
+ public static bool UseManagedNetworking
+ {
+ get
{
- get
+ if (s_useManagedNetworking != SwitchValue.None)
{
- if (s_enableUserAgent == Tristate.NotInitialized)
- {
- if (AppContext.TryGetSwitch(EnableUserAgentString, out bool returnedValue) && returnedValue)
- {
- s_enableUserAgent = Tristate.True;
- }
- else
- {
- s_enableUserAgent = Tristate.False;
- }
- }
- return s_enableUserAgent == Tristate.True;
+ return s_useManagedNetworking == SwitchValue.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 (!OperatingSystem.IsWindows())
{
- if (s_globalizationInvariantMode == Tristate.NotInitialized)
- {
- // Check if invariant mode 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;
+ s_useManagedNetworking = SwitchValue.True;
+ return true;
}
- }
- #else
- ///
- /// .NET Framework does not support Globalization Invariant mode, so this will always be false.
- ///
- public static bool GlobalizationInvariantMode
- {
- get => false;
- }
- #endif
-
- #if NET
-
- #if _WINDOWS
- ///
- /// 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 (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue)
{
- 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;
+ s_useManagedNetworking = SwitchValue.True;
+ return true;
}
+
+ s_useManagedNetworking = SwitchValue.False;
+ return false;
}
- #else
- ///
- /// .NET Core on Unix does not support the native SNI, so this will always be true.
- ///
- public static bool UseManagedNetworking => true;
- #endif
-
- #else
- ///
- /// .NET Framework does not support the managed SNI, so this will always be false.
- ///
- public static bool UseManagedNetworking => false;
- #endif
-
- #if NETFRAMEWORK
- ///
- /// 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
+ }
+ #elif NET
+ ///
+ /// .NET Core on Unix does not support native SNI, so this will always be
+ /// true.
+ ///
+ public static bool UseManagedNetworking => true;
+ #else
+ ///
+ /// .NET Framework does not support the managed SNI, so this will always be
+ /// false.
+ ///
+ public static bool UseManagedNetworking => false;
+ #endif
+
+ ///
+ /// Specifies minimum login timeout to be set to 1 second instead of 0
+ /// seconds, to prevent a login attempt from waiting indefinitely.
+ ///
+ /// The default value of this switch is true.
+ ///
+ public static bool UseMinimumLoginTimeout =>
+ AcquireAndReturn(
+ UseMinimumLoginTimeoutString,
+ defaultValue: true,
+ ref s_useMinimumLoginTimeout);
+
+ #endregion
+
+ #region Helpers
+
+ ///
+ /// Acquires the value of the specified app context switch and stores it
+ /// in the given reference. Applies the default value if the switch isn't
+ /// set.
+ ///
+ /// If the cached value is already set, it is returned immediately without
+ /// attempting to re-acquire it.
+ ///
+ /// No attempt is made to prevent multiple threads from acquiring the same
+ /// switch value simultaneously. The worst that can happen is that the
+ /// switch is acquired more than once, and the last acquired value wins.
+ ///
+ /// The name of the app context switch.
+ /// The default value to use if the switch is not set.
+ /// A reference to variable to store the switch value in.
+ /// Returns the acquired value as a bool.
+ private static bool AcquireAndReturn(
+ string switchName,
+ bool defaultValue,
+ ref SwitchValue switchValue)
+ {
+ // Refuse to re-acquire a switch value. Simply return whatever value
+ // was previously acquired.
+ if (switchValue != SwitchValue.None)
{
- 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;
- }
+ return switchValue == SwitchValue.True;
}
-#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
+
+ // Attempt to acquire the switch value from AppContext.
+ if (! AppContext.TryGetSwitch(switchName, out bool acquiredValue))
{
- 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;
- }
+ // The switch has no value, so use the given default.
+ switchValue = defaultValue ? SwitchValue.True : SwitchValue.False;
+ return defaultValue;
}
+
+ // Assign the appropriate SwitchValue based on the acquired value.
+ switchValue = acquiredValue ? SwitchValue.True : SwitchValue.False;
+ return acquiredValue;
}
+
+ #endregion
}
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
index ffcd2869f9..ec728d6a98 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
@@ -8,6 +8,7 @@
$(ObjFolder)$(Configuration).$(Platform).$(AssemblyName)
$(BinFolder)$(Configuration).$(Platform).$(AssemblyName)
true
+ enable
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
index f97aedfe4e..e3dd3902f0 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
@@ -67,7 +67,7 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable csc.Location == storeLocation && csc.Name == storeName);
+ CertificateStoreContext? storeContext = _certificateStoreModifications.Find(csc => csc.Location == storeLocation && csc.Name == storeName);
if (storeContext == null)
{
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
index a4aa84842f..0c51a7e17b 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
@@ -25,7 +25,7 @@ public sealed class ColumnEncryptionCertificateFixture : CertificateFixtureBase
public X509Certificate2 CertificateWithoutPrivateKey { get; }
private readonly X509Certificate2 _currentUserCertificate;
- private readonly X509Certificate2 _localMachineCertificate;
+ private readonly X509Certificate2? _localMachineCertificate;
public ColumnEncryptionCertificateFixture()
{
@@ -57,11 +57,19 @@ public ColumnEncryptionCertificateFixture()
public X509Certificate2 GetCertificate(StoreLocation storeLocation)
{
- return storeLocation == StoreLocation.CurrentUser
- ? _currentUserCertificate
- : storeLocation == StoreLocation.LocalMachine && IsAdmin
- ? _localMachineCertificate
- : throw new InvalidOperationException("Attempted to retrieve the certificate added to the local machine store; this requires administrator rights.");
+ if (storeLocation == StoreLocation.CurrentUser)
+ {
+ return _currentUserCertificate;
+ }
+
+ if (storeLocation == StoreLocation.LocalMachine &&
+ IsAdmin &&
+ _localMachineCertificate is not null)
+ {
+ return _localMachineCertificate;
+ }
+
+ throw new InvalidOperationException("Attempted to retrieve the certificate added to the local machine store; this requires administrator rights.");
}
public static bool IsAdmin
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
index a91ca7a0e0..7b431782af 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
@@ -21,7 +21,7 @@ public ColumnMasterKeyCertificateFixture()
{
}
- public X509Certificate2 ColumnMasterKeyCertificate { get; }
+ public X509Certificate2? ColumnMasterKeyCertificate { get; }
protected ColumnMasterKeyCertificateFixture(bool createCertificate)
{
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
index 74c4ca0325..56463e91b8 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
@@ -32,17 +32,17 @@ public CspCertificateFixture()
public string CspCertificatePath { get; }
- public string CspKeyPath { get; }
+ public string? CspKeyPath { get; }
- private string GetCspPathFromCertificate()
+ private string? GetCspPathFromCertificate()
{
- RSA privateKey = CspCertificate.GetRSAPrivateKey();
+ RSA? privateKey = CspCertificate.GetRSAPrivateKey();
if (privateKey is RSACryptoServiceProvider csp)
{
return string.Concat(csp.CspKeyContainerInfo.ProviderName, @"/", csp.CspKeyContainerInfo.KeyContainerName);
}
- else if (privateKey is RSACng cng)
+ else if (privateKey is RSACng cng && cng.Key.Provider is not null)
{
return string.Concat(cng.Key.Provider.Provider, @"/", cng.Key.KeyName);
}
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs
index 7d90df85fe..e71d82b398 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs
@@ -1,106 +1,61 @@
using System;
-using System.Collections.Generic;
using System.Reflection;
+using System.Threading;
namespace Microsoft.Data.SqlClient.Tests.Common;
///
-/// This class provides read/write access to LocalAppContextSwitches values
-/// for the duration of a test. It is intended to be constructed at the start
-/// of a test and disposed of at the end. It captures the original values of
-/// the switches and restores them when disposed.
+/// This class provides read/write access to LocalAppContextSwitches values for
+/// the duration of a test. It is intended to be constructed at the start of a
+/// test and disposed of at the end. It captures the original values of the
+/// switches and restores them when disposed.
///
/// This follows the RAII pattern to ensure that the switches are always
/// restored, which is important for global state like LocalAppContextSwitches.
///
/// https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
-///
-/// This class is not thread-aware and should not be used concurrently.
+///
+/// As with all global state, care must be taken when using this class in tests
+/// that may run in parallel. This class enforces a single instance policy
+/// using a semaphore. Overlapping constructor calls will wait up to 5 seconds
+/// for the previous instance to be disposed. Any tests that use this class
+/// should not keep an instance alive for longer than 5 seconds, or they risk
+/// causing failures in other tests.
///
public sealed class LocalAppContextSwitchesHelper : IDisposable
{
#region Private Fields
- // These fields are used to expose LocalAppContextSwitches's properties.
- private readonly PropertyInfo _legacyRowVersionNullBehaviorProperty;
- private readonly PropertyInfo _suppressInsecureTlsWarningProperty;
- private readonly PropertyInfo _makeReadAsyncBlockingProperty;
- private readonly PropertyInfo _useMinimumLoginTimeoutProperty;
- private readonly PropertyInfo _legacyVarTimeZeroScaleBehaviourProperty;
- private readonly PropertyInfo _useCompatibilityProcessSniProperty;
- private readonly PropertyInfo _useCompatibilityAsyncBehaviourProperty;
- private readonly PropertyInfo _useConnectionPoolV2Property;
- private readonly PropertyInfo _truncateScaledDecimalProperty;
- private readonly PropertyInfo _ignoreServerProvidedFailoverPartner;
- private readonly PropertyInfo _enableUserAgent;
- private readonly PropertyInfo _enableMultiSubnetFailoverByDefaultProperty;
-#if NET
- private readonly PropertyInfo _globalizationInvariantModeProperty;
- #endif
-
- #if NET && _WINDOWS
- private readonly PropertyInfo _useManagedNetworkingProperty;
- #endif
-
+ ///
+ /// This semaphore ensures that only one instance of this class may exist at
+ /// a time.
+ ///
+ private static readonly SemaphoreSlim s_instanceLock = new(1, 1);
+
+ ///
+ /// These fields are used to capture the original switch values.
+ ///
#if NETFRAMEWORK
- private readonly PropertyInfo _disableTnirByDefaultProperty;
+ private readonly bool? _disableTnirByDefaultOriginal;
#endif
-
- // These fields are used to capture the original switch values.
- private readonly FieldInfo _legacyRowVersionNullBehaviorField;
- private readonly Tristate _legacyRowVersionNullBehaviorOriginal;
- private readonly FieldInfo _suppressInsecureTlsWarningField;
- private readonly Tristate _suppressInsecureTlsWarningOriginal;
- private readonly FieldInfo _makeReadAsyncBlockingField;
- private readonly Tristate _makeReadAsyncBlockingOriginal;
- private readonly FieldInfo _useMinimumLoginTimeoutField;
- private readonly Tristate _useMinimumLoginTimeoutOriginal;
- private readonly FieldInfo _legacyVarTimeZeroScaleBehaviourField;
- private readonly Tristate _legacyVarTimeZeroScaleBehaviourOriginal;
- private readonly FieldInfo _useCompatibilityProcessSniField;
- private readonly Tristate _useCompatibilityProcessSniOriginal;
- private readonly FieldInfo _useCompatibilityAsyncBehaviourField;
- private readonly Tristate _useCompatibilityAsyncBehaviourOriginal;
- private readonly FieldInfo _useConnectionPoolV2Field;
- private readonly Tristate _useConnectionPoolV2Original;
- private readonly FieldInfo _truncateScaledDecimalField;
- private readonly Tristate _truncateScaledDecimalOriginal;
- private readonly FieldInfo _ignoreServerProvidedFailoverPartnerField;
- private readonly Tristate _ignoreServerProvidedFailoverPartnerOriginal;
- private readonly FieldInfo _enableUserAgentField;
- private readonly Tristate _enableUserAgentOriginal;
- private readonly FieldInfo _multiSubnetFailoverByDefaultField;
- private readonly Tristate _multiSubnetFailoverByDefaultOriginal;
-#if NET
- private readonly FieldInfo _globalizationInvariantModeField;
- private readonly Tristate _globalizationInvariantModeOriginal;
+ private readonly bool? _enableMultiSubnetFailoverByDefaultOriginal;
+ private readonly bool? _enableUserAgentOriginal;
+ #if NET
+ private readonly bool? _globalizationInvariantModeOriginal;
#endif
-
+ private readonly bool? _ignoreServerProvidedFailoverPartnerOriginal;
+ private readonly bool? _legacyRowVersionNullBehaviorOriginal;
+ private readonly bool? _legacyVarTimeZeroScaleBehaviourOriginal;
+ private readonly bool? _makeReadAsyncBlockingOriginal;
+ private readonly bool? _suppressInsecureTlsWarningOriginal;
+ private readonly bool? _truncateScaledDecimalOriginal;
+ private readonly bool? _useCompatibilityAsyncBehaviourOriginal;
+ private readonly bool? _useCompatibilityProcessSniOriginal;
+ private readonly bool? _useConnectionPoolV2Original;
#if NET && _WINDOWS
- private readonly FieldInfo _useManagedNetworkingField;
- private readonly Tristate _useManagedNetworkingOriginal;
+ private readonly bool? _useManagedNetworkingOriginal;
#endif
-
- #if NETFRAMEWORK
- private readonly FieldInfo _disableTnirByDefaultField;
- private readonly Tristate _disableTnirByDefaultOriginal;
- #endif
-
- #endregion
-
- #region Public Types
-
- ///
- /// This enum is used to represent the state of a switch.
- ///
- /// It is a copy of the Tristate enum from LocalAppContextSwitches.
- ///
- public enum Tristate : byte
- {
- NotInitialized = 0,
- False = 1,
- True = 2
- }
+ private readonly bool? _useMinimumLoginTimeoutOriginal;
#endregion
@@ -108,585 +63,379 @@ public enum Tristate : byte
///
/// Construct to capture all existing switch values.
+ ///
+ /// This call will block for at most 5 seconds, waiting for any previous
+ /// instance to be disposed before completing construction. Failure to
+ /// acquire the lock in that time will result in an exception being thrown.
///
- ///
- ///
- /// Throws if any values cannot be captured.
- ///
public LocalAppContextSwitchesHelper()
{
- // Acquire a handle to the LocalAppContextSwitches type.
- var assembly = typeof(SqlCommandBuilder).Assembly;
- var switchesType = assembly.GetType(
- "Microsoft.Data.SqlClient.LocalAppContextSwitches");
- if (switchesType == null)
+ // Wait for any previous instance to be disposed.
+ //
+ // We are only willing to wait a short time to avoid deadlocks.
+ //
+ if (! s_instanceLock.Wait(TimeSpan.FromSeconds(5)))
{
- throw new Exception("Unable to find LocalAppContextSwitches type.");
+ throw new InvalidOperationException(
+ "Timeout waiting for previous LocalAppContextSwitchesHelper " +
+ "instance to be disposed.");
}
- // A local helper to acquire a handle to a property.
- void InitProperty(string name, out PropertyInfo property)
+ try
{
- var prop = switchesType.GetProperty(
- name, BindingFlags.Public | BindingFlags.Static);
- if (prop == null)
- {
- throw new Exception($"Unable to find {name} property.");
- }
- property = prop;
+ #if NETFRAMEWORK
+ _disableTnirByDefaultOriginal =
+ GetSwitchValue("s_disableTnirByDefault");
+ #endif
+ _enableMultiSubnetFailoverByDefaultOriginal =
+ GetSwitchValue("s_enableMultiSubnetFailoverByDefault");
+ _enableUserAgentOriginal =
+ GetSwitchValue("s_enableUserAgent");
+ #if NET
+ _globalizationInvariantModeOriginal =
+ GetSwitchValue("s_globalizationInvariantMode");
+ #endif
+ _ignoreServerProvidedFailoverPartnerOriginal =
+ GetSwitchValue("s_ignoreServerProvidedFailoverPartner");
+ _legacyRowVersionNullBehaviorOriginal =
+ GetSwitchValue("s_legacyRowVersionNullBehavior");
+ _legacyVarTimeZeroScaleBehaviourOriginal =
+ GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour");
+ _makeReadAsyncBlockingOriginal =
+ GetSwitchValue("s_makeReadAsyncBlocking");
+ _suppressInsecureTlsWarningOriginal =
+ GetSwitchValue("s_suppressInsecureTlsWarning");
+ _truncateScaledDecimalOriginal =
+ GetSwitchValue("s_truncateScaledDecimal");
+ _useCompatibilityAsyncBehaviourOriginal =
+ GetSwitchValue("s_useCompatibilityAsyncBehaviour");
+ _useCompatibilityProcessSniOriginal =
+ GetSwitchValue("s_useCompatibilityProcessSni");
+ _useConnectionPoolV2Original =
+ GetSwitchValue("s_useConnectionPoolV2");
+ #if NET && _WINDOWS
+ _useManagedNetworkingOriginal =
+ GetSwitchValue("s_useManagedNetworking");
+ #endif
+ _useMinimumLoginTimeoutOriginal =
+ GetSwitchValue("s_useMinimumLoginTimeout");
}
-
- // Acquire handles to all of the public properties of
- // LocalAppContextSwitches.
- InitProperty(
- "LegacyRowVersionNullBehavior",
- out _legacyRowVersionNullBehaviorProperty);
-
- InitProperty(
- "SuppressInsecureTlsWarning",
- out _suppressInsecureTlsWarningProperty);
-
- InitProperty(
- "MakeReadAsyncBlocking",
- out _makeReadAsyncBlockingProperty);
-
- InitProperty(
- "UseMinimumLoginTimeout",
- out _useMinimumLoginTimeoutProperty);
-
- InitProperty(
- "LegacyVarTimeZeroScaleBehaviour",
- out _legacyVarTimeZeroScaleBehaviourProperty);
-
- InitProperty(
- "UseCompatibilityProcessSni",
- out _useCompatibilityProcessSniProperty);
-
- InitProperty(
- "UseCompatibilityAsyncBehaviour",
- out _useCompatibilityAsyncBehaviourProperty);
-
- InitProperty(
- "UseConnectionPoolV2",
- out _useConnectionPoolV2Property);
-
- InitProperty(
- "TruncateScaledDecimal",
- out _truncateScaledDecimalProperty);
-
- InitProperty(
- "IgnoreServerProvidedFailoverPartner",
- out _ignoreServerProvidedFailoverPartner);
-
- InitProperty(
- "EnableUserAgent",
- out _enableUserAgent);
-
- InitProperty(
- "EnableMultiSubnetFailoverByDefault",
- out _enableMultiSubnetFailoverByDefaultProperty);
-
-#if NET
- InitProperty(
- "GlobalizationInvariantMode",
- out _globalizationInvariantModeProperty);
- #endif
-
- #if NET && _WINDOWS
- InitProperty(
- "UseManagedNetworking",
- out _useManagedNetworkingProperty);
- #endif
-
- #if NETFRAMEWORK
- InitProperty(
- "DisableTnirByDefault",
- out _disableTnirByDefaultProperty);
- #endif
-
- // A local helper to capture the original value of a switch.
- void InitField(string name, out FieldInfo field, out Tristate value)
+ catch
{
- var fieldInfo =
- switchesType.GetField(
- name, BindingFlags.NonPublic | BindingFlags.Static);
- if (fieldInfo == null)
- {
- throw new Exception($"Unable to find {name} field.");
- }
- field = fieldInfo;
- value = GetValue(field);
+ // If we fail to capture the original values, release the lock
+ // immediately to avoid deadlocks.
+ s_instanceLock.Release();
+ throw;
}
-
- // Capture the original value of each switch.
- InitField(
- "s_legacyRowVersionNullBehavior",
- out _legacyRowVersionNullBehaviorField,
- out _legacyRowVersionNullBehaviorOriginal);
-
- InitField(
- "s_suppressInsecureTlsWarning",
- out _suppressInsecureTlsWarningField,
- out _suppressInsecureTlsWarningOriginal);
-
- InitField(
- "s_makeReadAsyncBlocking",
- out _makeReadAsyncBlockingField,
- out _makeReadAsyncBlockingOriginal);
-
- InitField(
- "s_useMinimumLoginTimeout",
- out _useMinimumLoginTimeoutField,
- out _useMinimumLoginTimeoutOriginal);
-
- InitField(
- "s_legacyVarTimeZeroScaleBehaviour",
- out _legacyVarTimeZeroScaleBehaviourField,
- out _legacyVarTimeZeroScaleBehaviourOriginal);
-
- InitField(
- "s_useCompatibilityProcessSni",
- out _useCompatibilityProcessSniField,
- out _useCompatibilityProcessSniOriginal);
-
- InitField(
- "s_useCompatibilityAsyncBehaviour",
- out _useCompatibilityAsyncBehaviourField,
- out _useCompatibilityAsyncBehaviourOriginal);
-
- InitField(
- "s_useConnectionPoolV2",
- out _useConnectionPoolV2Field,
- out _useConnectionPoolV2Original);
-
- InitField(
- "s_truncateScaledDecimal",
- out _truncateScaledDecimalField,
- out _truncateScaledDecimalOriginal);
-
- InitField(
- "s_ignoreServerProvidedFailoverPartner",
- out _ignoreServerProvidedFailoverPartnerField,
- out _ignoreServerProvidedFailoverPartnerOriginal);
-
- InitField(
- "s_enableUserAgent",
- out _enableUserAgentField,
- out _enableUserAgentOriginal);
-
- InitField(
- "s_multiSubnetFailoverByDefault",
- out _multiSubnetFailoverByDefaultField,
- out _multiSubnetFailoverByDefaultOriginal);
-
-#if NET
- InitField(
- "s_globalizationInvariantMode",
- out _globalizationInvariantModeField,
- out _globalizationInvariantModeOriginal);
- #endif
-
- #if NET && _WINDOWS
- InitField(
- "s_useManagedNetworking",
- out _useManagedNetworkingField,
- out _useManagedNetworkingOriginal);
-#endif
-
- #if NETFRAMEWORK
- InitField(
- "s_disableTnirByDefault",
- out _disableTnirByDefaultField,
- out _disableTnirByDefaultOriginal);
- #endif
}
///
- /// Disposal restores all original switch values as a best effort.
+ /// Disposal restores all original switch values and releases the instance
+ /// lock.
///
- ///
- ///
- /// Throws if any values could not be restored after trying to restore all
- /// values.
- ///
public void Dispose()
{
- List failedFields = new();
-
- void RestoreField(FieldInfo field, Tristate value)
+ try
{
- try
- {
- SetValue(field, value);
- }
- catch (Exception)
- {
- failedFields.Add(field.Name);
- }
+ #if NETFRAMEWORK
+ SetSwitchValue(
+ "s_disableTnirByDefault",
+ _disableTnirByDefaultOriginal);
+ #endif
+ SetSwitchValue(
+ "s_enableMultiSubnetFailoverByDefault",
+ _enableMultiSubnetFailoverByDefaultOriginal);
+ SetSwitchValue(
+ "s_enableUserAgent",
+ _enableUserAgentOriginal);
+ #if NET
+ SetSwitchValue(
+ "s_globalizationInvariantMode",
+ _globalizationInvariantModeOriginal);
+ #endif
+ SetSwitchValue(
+ "s_ignoreServerProvidedFailoverPartner",
+ _ignoreServerProvidedFailoverPartnerOriginal);
+ SetSwitchValue(
+ "s_legacyRowVersionNullBehavior",
+ _legacyRowVersionNullBehaviorOriginal);
+ SetSwitchValue(
+ "s_legacyVarTimeZeroScaleBehaviour",
+ _legacyVarTimeZeroScaleBehaviourOriginal);
+ SetSwitchValue(
+ "s_makeReadAsyncBlocking",
+ _makeReadAsyncBlockingOriginal);
+ SetSwitchValue(
+ "s_suppressInsecureTlsWarning",
+ _suppressInsecureTlsWarningOriginal);
+ SetSwitchValue(
+ "s_truncateScaledDecimal",
+ _truncateScaledDecimalOriginal);
+ SetSwitchValue(
+ "s_useCompatibilityAsyncBehaviour",
+ _useCompatibilityAsyncBehaviourOriginal);
+ SetSwitchValue(
+ "s_useCompatibilityProcessSni",
+ _useCompatibilityProcessSniOriginal);
+ SetSwitchValue(
+ "s_useConnectionPoolV2",
+ _useConnectionPoolV2Original);
+ #if NET && _WINDOWS
+ SetSwitchValue(
+ "s_useManagedNetworking",
+ _useManagedNetworkingOriginal);
+ #endif
+ SetSwitchValue(
+ "s_useMinimumLoginTimeout",
+ _useMinimumLoginTimeoutOriginal);
}
-
- RestoreField(
- _legacyRowVersionNullBehaviorField,
- _legacyRowVersionNullBehaviorOriginal);
-
- RestoreField(
- _suppressInsecureTlsWarningField,
- _suppressInsecureTlsWarningOriginal);
-
- RestoreField(
- _makeReadAsyncBlockingField,
- _makeReadAsyncBlockingOriginal);
-
- RestoreField(
- _useMinimumLoginTimeoutField,
- _useMinimumLoginTimeoutOriginal);
-
- RestoreField(
- _legacyVarTimeZeroScaleBehaviourField,
- _legacyVarTimeZeroScaleBehaviourOriginal);
-
- RestoreField(
- _useCompatibilityProcessSniField,
- _useCompatibilityProcessSniOriginal);
-
- RestoreField(
- _useCompatibilityAsyncBehaviourField,
- _useCompatibilityAsyncBehaviourOriginal);
-
- RestoreField(
- _useConnectionPoolV2Field,
- _useConnectionPoolV2Original);
-
- RestoreField(
- _truncateScaledDecimalField,
- _truncateScaledDecimalOriginal);
-
- RestoreField(
- _ignoreServerProvidedFailoverPartnerField,
- _ignoreServerProvidedFailoverPartnerOriginal);
-
- RestoreField(
- _enableUserAgentField,
- _enableUserAgentOriginal);
-
- RestoreField(
- _multiSubnetFailoverByDefaultField,
- _multiSubnetFailoverByDefaultOriginal);
-
- #if NET
- RestoreField(
- _globalizationInvariantModeField,
- _globalizationInvariantModeOriginal);
- #endif
-
- #if NET && _WINDOWS
- RestoreField(
- _useManagedNetworkingField,
- _useManagedNetworkingOriginal);
- #endif
-
- #if NETFRAMEWORK
- RestoreField(
- _disableTnirByDefaultField,
- _disableTnirByDefaultOriginal);
- #endif
-
- if (failedFields.Count > 0)
+ finally
{
- throw new Exception(
- "Failed to restore the following fields: " +
- string.Join(", ", failedFields));
+ // Release the lock to allow another instance to be created.
+ s_instanceLock.Release();
}
}
#endregion
- #region Public Properties
-
- ///
- /// Access the LocalAppContextSwitches.LegacyRowVersionNullBehavior
- /// property.
- ///
- public bool LegacyRowVersionNullBehavior
- {
- get => (bool)_legacyRowVersionNullBehaviorProperty.GetValue(null);
- }
+ #region Switch Value Getters and Setters
- ///
- /// Access the LocalAppContextSwitches.SuppressInsecureTlsWarning property.
- ///
- public bool SuppressInsecureTlsWarning
- {
- get => (bool)_suppressInsecureTlsWarningProperty.GetValue(null);
- }
+ // These properties get or set the like-named underlying switch field value.
+ //
+ // They all throw if the value cannot be retrieved or set.
+ #if NETFRAMEWORK
///
- /// Access the LocalAppContextSwitches.MakeReadAsyncBlocking property.
+ /// Get or set the DisableTnirByDefault switch value.
///
- public bool MakeReadAsyncBlocking
+ public bool? DisableTnirByDefault
{
- get => (bool)_makeReadAsyncBlockingProperty.GetValue(null);
+ get => GetSwitchValue("s_disableTnirByDefault");
+ set => SetSwitchValue("s_disableTnirByDefault", value);
}
+ #endif
///
- /// Access the LocalAppContextSwitches.UseMinimumLoginTimeout property.
+ /// Get or set the EnableMultiSubnetFailoverByDefault switch value.
///
- public bool UseMinimumLoginTimeout
+ public bool? EnableMultiSubnetFailoverByDefault
{
- get => (bool)_useMinimumLoginTimeoutProperty.GetValue(null);
+ get => GetSwitchValue("s_enableMultiSubnetFailoverByDefault");
+ set => SetSwitchValue("s_enableMultiSubnetFailoverByDefault", value);
}
///
- /// Access the LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour
- /// property.
+ /// Get or set the EnableUserAgent switch value.
///
- public bool LegacyVarTimeZeroScaleBehaviour
+ public bool? EnableUserAgent
{
- get => (bool)_legacyVarTimeZeroScaleBehaviourProperty.GetValue(null);
+ get => GetSwitchValue("s_enableUserAgent");
+ set => SetSwitchValue("s_enableUserAgent", value);
}
+ #if NET
///
- /// Access the LocalAppContextSwitches.UseCompatibilityProcessSni property.
+ /// Get or set the GlobalizationInvariantMode switch value.
///
- public bool UseCompatibilityProcessSni
+ public bool? GlobalizationInvariantMode
{
- get => (bool)_useCompatibilityProcessSniProperty.GetValue(null);
+ get => GetSwitchValue("s_globalizationInvariantMode");
+ set => SetSwitchValue("s_globalizationInvariantMode", value);
}
+ #endif
///
- /// Access the LocalAppContextSwitches.UseCompatibilityAsyncBehaviour
- /// property.
+ /// Get or set the IgnoreServerProvidedFailoverPartner switch value.
///
- public bool UseCompatibilityAsyncBehaviour
+ public bool? IgnoreServerProvidedFailoverPartner
{
- get => (bool)_useCompatibilityAsyncBehaviourProperty.GetValue(null);
+ get => GetSwitchValue("s_ignoreServerProvidedFailoverPartner");
+ set => SetSwitchValue("s_ignoreServerProvidedFailoverPartner", value);
}
///
- /// Access the LocalAppContextSwitches.UseConnectionPoolV2 property.
+ /// Get or set the LegacyRowVersionNullBehavior switch value.
///
- public bool UseConnectionPoolV2
+ public bool? LegacyRowVersionNullBehavior
{
- get => (bool)_useConnectionPoolV2Property.GetValue(null);
+ get => GetSwitchValue("s_legacyRowVersionNullBehavior");
+ set => SetSwitchValue("s_legacyRowVersionNullBehavior", value);
}
///
- /// Access the LocalAppContextSwitches.TruncateScaledDecimal property.
+ /// Get or set the LegacyVarTimeZeroScaleBehaviour switch value.
///
- public bool TruncateScaledDecimal
- {
- get => (bool)_truncateScaledDecimalProperty.GetValue(null);
- }
-
- public bool IgnoreServerProvidedFailoverPartner
- {
- get => (bool)_ignoreServerProvidedFailoverPartner.GetValue(null);
- }
-
- public bool EnableUserAgent
- {
- get => (bool)_enableUserAgent.GetValue(null);
- }
-
- public bool EnableMultiSubnetFailoverByDefault
+ public bool? LegacyVarTimeZeroScaleBehaviour
{
- get => (bool)_enableMultiSubnetFailoverByDefaultProperty.GetValue(null);
+ get => GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour");
+ set => SetSwitchValue("s_legacyVarTimeZeroScaleBehaviour", value);
}
- #if NET
///
- /// Access the LocalAppContextSwitches.GlobalizationInvariantMode property.
+ /// Get or set the MakeReadAsyncBlocking switch value.
///
- public bool GlobalizationInvariantMode
+ public bool? MakeReadAsyncBlocking
{
- get => (bool)_globalizationInvariantModeProperty.GetValue(null);
+ get => GetSwitchValue("s_makeReadAsyncBlocking");
+ set => SetSwitchValue("s_makeReadAsyncBlocking", value);
}
- #endif
- #if NET && _WINDOWS
- ///
- /// Access the LocalAppContextSwitches.UseManagedNetworking property.
- ///
- public bool UseManagedNetworking
- {
- get => (bool)_useManagedNetworkingProperty.GetValue(null);
- }
- #endif
-
- #if NETFRAMEWORK
///
- /// Access the LocalAppContextSwitches.DisableTnirByDefault property.
+ /// Get or set the SuppressInsecureTlsWarning switch value.
///
- public bool DisableTnirByDefault
+ public bool? SuppressInsecureTlsWarning
{
- get => (bool)_disableTnirByDefaultProperty.GetValue(null);
+ get => GetSwitchValue("s_suppressInsecureTlsWarning");
+ set => SetSwitchValue("s_suppressInsecureTlsWarning", value);
}
- #endif
-
- // 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.
///
- /// Get or set the LocalAppContextSwitches.LegacyRowVersionNullBehavior
- /// switch value.
+ /// Get or set the TruncateScaledDecimal switch value.
///
- public Tristate LegacyRowVersionNullBehaviorField
+ public bool? TruncateScaledDecimal
{
- get => GetValue(_legacyRowVersionNullBehaviorField);
- set => SetValue(_legacyRowVersionNullBehaviorField, value);
+ get => GetSwitchValue("s_truncateScaledDecimal");
+ set => SetSwitchValue("s_truncateScaledDecimal", value);
}
///
- /// Get or set the LocalAppContextSwitches.SuppressInsecureTlsWarning
- /// switch value.
+ /// Get or set the UseCompatibilityAsyncBehaviour switch value.
///
- public Tristate SuppressInsecureTlsWarningField
+ public bool? UseCompatibilityAsyncBehaviour
{
- get => GetValue(_suppressInsecureTlsWarningField);
- set => SetValue(_suppressInsecureTlsWarningField, value);
+ get => GetSwitchValue("s_useCompatibilityAsyncBehaviour");
+ set => SetSwitchValue("s_useCompatibilityAsyncBehaviour", value);
}
///
- /// Get or set the LocalAppContextSwitches.MakeReadAsyncBlocking switch
- /// value.
+ /// Get or set the UseCompatibilityProcessSni switch value.
///
- public Tristate MakeReadAsyncBlockingField
+ public bool? UseCompatibilityProcessSni
{
- get => GetValue(_makeReadAsyncBlockingField);
- set => SetValue(_makeReadAsyncBlockingField, value);
+ get => GetSwitchValue("s_useCompatibilityProcessSni");
+ set => SetSwitchValue("s_useCompatibilityProcessSni", value);
}
///
- /// Get or set the LocalAppContextSwitches.UseMinimumLoginTimeout switch
- /// value.
+ /// Get or set the UseConnectionPoolV2 switch value.
///
- public Tristate UseMinimumLoginTimeoutField
+ public bool? UseConnectionPoolV2
{
- get => GetValue(_useMinimumLoginTimeoutField);
- set => SetValue(_useMinimumLoginTimeoutField, value);
+ get => GetSwitchValue("s_useConnectionPoolV2");
+ set => SetSwitchValue("s_useConnectionPoolV2", value);
}
+ #if NET && _WINDOWS
///
- /// Get or set the LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour
- /// switch value.
+ /// Get or set the UseManagedNetworking switch value.
///
- public Tristate LegacyVarTimeZeroScaleBehaviourField
+ public bool? UseManagedNetworking
{
- get => GetValue(_legacyVarTimeZeroScaleBehaviourField);
- set => SetValue(_legacyVarTimeZeroScaleBehaviourField, value);
+ get => GetSwitchValue("s_useManagedNetworking");
+ set => SetSwitchValue("s_useManagedNetworking", value);
}
+ #endif
///
- /// Get or set the LocalAppContextSwitches.UseCompatibilityProcessSni switch
- /// value.
+ /// Get or set the UseMinimumLoginTimeout switch value.
///
- public Tristate UseCompatibilityProcessSniField
+ public bool? UseMinimumLoginTimeout
{
- get => GetValue(_useCompatibilityProcessSniField);
- set => SetValue(_useCompatibilityProcessSniField, value);
+ get => GetSwitchValue("s_useMinimumLoginTimeout");
+ set => SetSwitchValue("s_useMinimumLoginTimeout", value);
}
- ///
- /// Get or set the LocalAppContextSwitches.UseCompatibilityAsyncBehaviour
- /// switch value.
- ///
- public Tristate UseCompatibilityAsyncBehaviourField
- {
- get => GetValue(_useCompatibilityAsyncBehaviourField);
- set => SetValue(_useCompatibilityAsyncBehaviourField, value);
- }
+ #endregion
- ///
- /// Get or set the LocalAppContextSwitches.UseConnectionPoolV2 switch value.
- ///
- public Tristate UseConnectionPoolV2Field
- {
- get => GetValue(_useConnectionPoolV2Field);
- set => SetValue(_useConnectionPoolV2Field, value);
- }
+ #region Helpers
///
- /// Get or set the LocalAppContextSwitches.TruncateScaledDecimal switch value.
+ /// Use reflection to get a switch field value from LocalAppContextSwitches.
///
- public Tristate TruncateScaledDecimalField
+ private static bool? GetSwitchValue(string fieldName)
{
- get => GetValue(_truncateScaledDecimalField);
- set => SetValue(_truncateScaledDecimalField, value);
- }
-
- public Tristate IgnoreServerProvidedFailoverPartnerField
- {
- get => GetValue(_ignoreServerProvidedFailoverPartnerField);
- set => SetValue(_ignoreServerProvidedFailoverPartnerField, value);
- }
+ var assembly = Assembly.GetAssembly(typeof(SqlConnection));
+ if (assembly is null)
+ {
+ throw new InvalidOperationException(
+ "Could not get assembly for Microsoft.Data.SqlClient");
+ }
+
+ var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches");
+ if (type is null)
+ {
+ throw new InvalidOperationException(
+ "Could not get type LocalAppContextSwitches");
+ }
- public Tristate EnableUserAgentField
- {
- get => GetValue(_enableUserAgentField);
- set => SetValue(_enableUserAgentField, value);
- }
+ var field = type.GetField(
+ fieldName,
+ BindingFlags.Static | BindingFlags.NonPublic);
+ if (field == null)
+ {
+ throw new InvalidOperationException(
+ $"Field '{fieldName}' not found in LocalAppContextSwitches");
+ }
- public Tristate EnableMultiSubnetFailoverByDefaultField
- {
- get => GetValue(_multiSubnetFailoverByDefaultField);
- set => SetValue(_multiSubnetFailoverByDefaultField, value);
- }
+ var value = field.GetValue(null);
+ if (value is not null)
+ {
+ // GOTCHA: This assumes that switch values map to bytes as:
+ //
+ // None = 0
+ // True = 1
+ // False = 2
+ //
+ // See the LocalAppContextSwitches.SwitchValue enum definition.
+ //
+ byte underlyingValue = (byte)value;
+ return underlyingValue == 0 ? null : underlyingValue == 1;
+ }
-#if NET
- ///
- /// Get or set the LocalAppContextSwitches.GlobalizationInvariantMode switch value.
- ///
- public Tristate GlobalizationInvariantModeField
- {
- get => GetValue(_globalizationInvariantModeField);
- set => SetValue(_globalizationInvariantModeField, value);
+ throw new InvalidOperationException(
+ $"Field '{fieldName}' is not of type byte");
}
- #endif
- #if NET && _WINDOWS
- ///
- /// Get or set the LocalAppContextSwitches.UseManagedNetworking switch value.
- ///
- public Tristate UseManagedNetworkingField
- {
- get => GetValue(_useManagedNetworkingField);
- set => SetValue(_useManagedNetworkingField, value);
- }
- #endif
-
- #if NETFRAMEWORK
///
- /// Get or set the LocalAppContextSwitches.DisableTnirByDefault switch
- /// value.
+ /// Use reflection to set a switch field value in LocalAppContextSwitches.
///
- public Tristate DisableTnirByDefaultField
- {
- get => GetValue(_disableTnirByDefaultField);
- set => SetValue(_disableTnirByDefaultField, value);
- }
- #endif
-
- #endregion
-
- #region Private Helpers
-
- // Get the value of the given field, or throw if it is null.
- private static Tristate GetValue(FieldInfo field)
+ private static void SetSwitchValue(string fieldName, bool? value)
{
- var value = field.GetValue(null);
- if (value is null)
+ var assembly = Assembly.GetAssembly(typeof(SqlConnection));
+ if (assembly is null)
+ {
+ throw new InvalidOperationException(
+ "Could not get assembly for Microsoft.Data.SqlClient");
+ }
+
+ var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches");
+ if (type is null)
{
- throw new Exception($"Field {field.Name} has a null value.");
+ throw new InvalidOperationException(
+ "Could not get type LocalAppContextSwitches");
}
- return (Tristate)value;
- }
+ var field = type.GetField(
+ fieldName,
+ BindingFlags.Static | BindingFlags.NonPublic);
+ if (field == null)
+ {
+ throw new InvalidOperationException(
+ $"Field '{fieldName}' not found in LocalAppContextSwitches");
+ }
- // Set the value of the given field.
- private static void SetValue(FieldInfo field, Tristate value)
- {
- field.SetValue(null, (byte)value);
+ // GOTCHA: This assumes that switch values map to bytes as:
+ //
+ // None = 0
+ // True = 1
+ // False = 2
+ //
+ // See the LocalAppContextSwitches.SwitchValue enum definition.
+ //
+ byte byteValue =
+ (byte)(!value.HasValue ? 0 : value.Value ? 1 : 2);
+
+ field.SetValue(null, Enum.ToObject(field.FieldType, byteValue));
}
#endregion
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs
index f62c5a70c8..8465d894a9 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs
@@ -1952,10 +1952,8 @@ public void SqlDateTime2Scale_Legacy(int? setScale, byte outputScale, bool legac
lock (_parameterLegacyScaleLock)
{
using SwitchesHelper switches = new SwitchesHelper();
- switches.LegacyVarTimeZeroScaleBehaviourField =
- legacyVarTimeZeroScaleSwitchValue
- ? SwitchesHelper.Tristate.True
- : SwitchesHelper.Tristate.False;
+ switches.LegacyVarTimeZeroScaleBehaviour =
+ legacyVarTimeZeroScaleSwitchValue;
var parameter = new SqlParameter
{
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs
index 6e8f64fcc8..4e70eaae57 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs
@@ -5,10 +5,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Reflection;
using Microsoft.Data.SqlClient.Tests;
-
-using SwitchesHelper = Microsoft.Data.SqlClient.Tests.Common.LocalAppContextSwitchesHelper;
+using Microsoft.Data.SqlClient.Tests.Common;
namespace Microsoft.Data.SqlClient
{
@@ -163,7 +161,25 @@ private void AssertValidState() { }
[DebuggerStepThrough]
private void AddError(object value) => throw new Exception(value as string ?? "AddError");
- private SwitchesHelper LocalAppContextSwitches = new();
+ private class SwitchesHelper : IDisposable
+ {
+ private readonly LocalAppContextSwitchesHelper _helper = new();
+
+ public void Dispose()
+ {
+ _helper.Dispose();
+ }
+
+ public bool UseCompatibilityProcessSni
+ {
+ get
+ {
+ var value = _helper.UseCompatibilityProcessSni;
+ return value.HasValue && value.Value;
+ }
+ }
+ }
+ private readonly SwitchesHelper LocalAppContextSwitches = new SwitchesHelper();
#if NETFRAMEWORK
private SniNativeWrapperImpl _native;
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
index b071eeaf5a..040119616e 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
@@ -268,7 +268,7 @@ public static void CheckNullRowVersionIsDBNull()
lock (s_rowVersionLock)
{
using SwitchesHelper helper = new();
- helper.LegacyRowVersionNullBehaviorField = SwitchesHelper.Tristate.False;
+ helper.LegacyRowVersionNullBehavior = false;
using SqlConnection con = new(DataTestUtility.TCPConnectionString);
con.Open();
@@ -869,7 +869,7 @@ public static void CheckLegacyNullRowVersionIsEmptyArray()
lock (s_rowVersionLock)
{
using SwitchesHelper helper = new();
- helper.LegacyRowVersionNullBehaviorField = SwitchesHelper.Tristate.True;
+ helper.LegacyRowVersionNullBehavior = true;
using SqlConnection con = new(DataTestUtility.TCPConnectionString);
con.Open();
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs
index 7ae4a82fb9..4574f81a93 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs
@@ -589,7 +589,7 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr
{
using (SqlCommand cmd = connection.CreateCommand())
{
- appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False;
+ appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal;
var p = new SqlParameter("@Value", null)
{
@@ -636,7 +636,7 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString,
}
bulkCopy.DestinationTableName = tableName;
- appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False;
+ appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal;
bulkCopy.WriteToServer(table);
}
Assert.True(ValidateInsertedValues(connection, tableName, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]");
@@ -681,7 +681,7 @@ public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool
table.Rows.Add(newRow);
}
p.Value = table;
- appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False;
+ appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal;
cmd.ExecuteNonQuery();
}
// TVP always rounds data without attention to the configuration.
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs
index 5a335e957e..49912202d6 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs
@@ -29,7 +29,9 @@ public static void RunTest()
Assert.Equal("12.3", value.ToString());
value = BulkCopySqlDecimalToTable(new SqlDecimal(123.45), 10, 2, 4, 1);
- if (appContextSwitches.TruncateScaledDecimal)
+
+ bool? truncate = appContextSwitches.TruncateScaledDecimal;
+ if (truncate.HasValue && truncate.Value)
{
Assert.Equal("123.4", value.ToString());
}
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 c92402af41..59445a26b6 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,12 +27,17 @@ public void TestDefaultAppContextSwitchValues()
Assert.True(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour);
Assert.False(LocalAppContextSwitches.UseConnectionPoolV2);
Assert.False(LocalAppContextSwitches.TruncateScaledDecimal);
+ Assert.False(LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner);
+ Assert.False(LocalAppContextSwitches.EnableUserAgent);
Assert.False(LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault);
-#if NETFRAMEWORK
- Assert.False(LocalAppContextSwitches.DisableTnirByDefault);
+ #if NET
+ Assert.False(LocalAppContextSwitches.GlobalizationInvariantMode);
+ #endif
+ #if NET && _WINDOWS
Assert.False(LocalAppContextSwitches.UseManagedNetworking);
-#else
- Assert.Equal(!OperatingSystem.IsWindows(), LocalAppContextSwitches.UseManagedNetworking);
-#endif
+ #endif
+ #if NETFRAMEWORK
+ Assert.False(LocalAppContextSwitches.DisableTnirByDefault);
+ #endif
}
}
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 e413821932..844506e92f 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
@@ -1,40 +1,41 @@
using System;
using Microsoft.Data.SqlClient.Tests.Common;
using Xunit;
-using static Microsoft.Data.SqlClient.Tests.Common.LocalAppContextSwitchesHelper;
namespace Microsoft.Data.SqlClient.UnitTests.Microsoft.Data.SqlClient
{
public class SqlConnectionStringTest : IDisposable
{
- private LocalAppContextSwitchesHelper _appContextSwitchHelper;
- public SqlConnectionStringTest()
+ // Ensure we restore the original app context switch values after each
+ // test.
+ private readonly LocalAppContextSwitchesHelper _appContextSwitchHelper = new();
+
+ public void Dispose()
{
- // Ensure that the app context switch is set to the default value
- _appContextSwitchHelper = new LocalAppContextSwitchesHelper();
+ _appContextSwitchHelper.Dispose();
}
#if NETFRAMEWORK
[Theory]
- [InlineData("test.database.windows.net", true, Tristate.True, true)]
- [InlineData("test.database.windows.net", false, Tristate.True, false)]
- [InlineData("test.database.windows.net", null, Tristate.True, false)]
- [InlineData("test.database.windows.net", true, Tristate.False, true)]
- [InlineData("test.database.windows.net", false, Tristate.False, false)]
- [InlineData("test.database.windows.net", null, Tristate.False, true)]
- [InlineData("test.database.windows.net", true, Tristate.NotInitialized, true)]
- [InlineData("test.database.windows.net", false, Tristate.NotInitialized, false)]
- [InlineData("test.database.windows.net", null, Tristate.NotInitialized, true)]
- [InlineData("my.test.server", true, Tristate.True, true)]
- [InlineData("my.test.server", false, Tristate.True, false)]
- [InlineData("my.test.server", null, Tristate.True, false)]
- [InlineData("my.test.server", true, Tristate.False, true)]
- [InlineData("my.test.server", false, Tristate.False, false)]
- [InlineData("my.test.server", null, Tristate.False, true)]
- [InlineData("my.test.server", true, Tristate.NotInitialized, true)]
- [InlineData("my.test.server", false, Tristate.NotInitialized, false)]
- [InlineData("my.test.server", null, Tristate.NotInitialized, true)]
- public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, Tristate tnirDisabledAppContext, bool expectedValue)
+ [InlineData("test.database.windows.net", true, true, true)]
+ [InlineData("test.database.windows.net", false, true, false)]
+ [InlineData("test.database.windows.net", null, true, false)]
+ [InlineData("test.database.windows.net", true, false, true)]
+ [InlineData("test.database.windows.net", false, false, false)]
+ [InlineData("test.database.windows.net", null, false, true)]
+ [InlineData("test.database.windows.net", true, null, true)]
+ [InlineData("test.database.windows.net", false, null, false)]
+ [InlineData("test.database.windows.net", null, null, true)]
+ [InlineData("my.test.server", true, true, true)]
+ [InlineData("my.test.server", false, true, false)]
+ [InlineData("my.test.server", null, true, false)]
+ [InlineData("my.test.server", true, false, true)]
+ [InlineData("my.test.server", false, false, false)]
+ [InlineData("my.test.server", null, false, true)]
+ [InlineData("my.test.server", true, null, true)]
+ [InlineData("my.test.server", false, null, false)]
+ [InlineData("my.test.server", null, null, true)]
+ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, bool? tnirDisabledAppContext, bool expectedValue)
{
// Note: TNIR is only supported on .NET Framework.
// Note: TNIR is disabled by default for Azure SQL Database servers (i.e. *.database.windows.net)
@@ -43,7 +44,7 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, Tr
// the value of TransparentNetworkIPResolution property in SqlConnectionString.
// Arrange
- _appContextSwitchHelper.DisableTnirByDefaultField = tnirDisabledAppContext;
+ _appContextSwitchHelper.DisableTnirByDefault = tnirDisabledAppContext;
// Act
SqlConnectionStringBuilder builder = new();
@@ -62,16 +63,16 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, Tr
/// 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)
+ [InlineData(true, true, true)]
+ [InlineData(false, true, false)]
+ [InlineData(null, true, true)]
+ [InlineData(true, false, true)]
+ [InlineData(false, false, false)]
+ [InlineData(null, false, false)]
+ [InlineData(null, null, false)]
+ public void TestDefaultMultiSubnetFailover(bool? msfInConnString, bool? msfEnabledAppContext, bool expectedValue)
{
- _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultField = msfEnabledAppContext;
+ _appContextSwitchHelper.EnableMultiSubnetFailoverByDefault = msfEnabledAppContext;
SqlConnectionStringBuilder builder = new();
if (msfInConnString.HasValue)
@@ -89,7 +90,7 @@ public void TestDefaultMultiSubnetFailover(bool? msfInConnString, Tristate msfEn
[Fact]
public void TestMultiSubnetFailoverWithFailoverPartnerThrows()
{
- _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultField = Tristate.True;
+ _appContextSwitchHelper.EnableMultiSubnetFailoverByDefault = true;
SqlConnectionStringBuilder builder = new()
{
@@ -100,11 +101,5 @@ public void TestMultiSubnetFailoverWithFailoverPartnerThrows()
Assert.Throws(() => new SqlConnectionString(builder.ConnectionString));
}
-
- public void Dispose()
- {
- // Clean up any resources if necessary
- _appContextSwitchHelper.Dispose();
- }
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs
index dfc37d2720..5b294ff20e 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs
@@ -529,7 +529,7 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs
{
// Arrange
using LocalAppContextSwitchesHelper switchesHelper = new();
- switchesHelper.IgnoreServerProvidedFailoverPartnerField = LocalAppContextSwitchesHelper.Tristate.True;
+ switchesHelper.IgnoreServerProvidedFailoverPartner = true;
using TdsServer failoverServer = new(
new TdsServerArguments
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs
index 9dad33aa1d..a6d2cac9fd 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs
@@ -839,7 +839,7 @@ public void TestConnWithUserAgentFeatureExtension(bool forceAck)
{
// Make sure needed switch is enabled
using LocalAppContextSwitchesHelper switchesHelper = new();
- switchesHelper.EnableUserAgentField = LocalAppContextSwitchesHelper.Tristate.True;
+ switchesHelper.EnableUserAgent = true;
using var server = new TdsServer();
server.Start();
@@ -935,7 +935,7 @@ public void TestConnWithoutUserAgentFeatureExtension()
{
// Disable the client-side UserAgent field entirely
using LocalAppContextSwitchesHelper switchesHelper = new();
- switchesHelper.EnableUserAgentField = LocalAppContextSwitchesHelper.Tristate.False;
+ switchesHelper.EnableUserAgent = false;
using var server = new TdsServer();
server.Start();