diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AuthenticationFeatureSwitches.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AuthenticationFeatureSwitches.cs
index a9a3ecc426..be278994e8 100644
--- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AuthenticationFeatureSwitches.cs
+++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AuthenticationFeatureSwitches.cs
@@ -8,7 +8,7 @@ namespace Microsoft.Data.SqlClient;
///
/// Defines authentication bootstrap switches and capability guards so configuration loading, Azure
-/// extension discovery can be excluded from trimmed or AOT builds.
+/// extension discovery, and runtime version checks can be excluded from trimmed or AOT builds.
///
///
/// Configuration and discovery default to enabled to preserve existing runtime behavior. Each
@@ -55,6 +55,14 @@ internal static class AuthenticationFeatureSwitches
#region Trimming Guards
+ /// Indicates whether bootstrap may inspect loaded SqlClient family assembly versions.
+ // Fixed trimmed/AOT images cannot replace assemblies after publishing.
+ [FeatureGuard(typeof(RequiresUnreferencedCodeAttribute))]
+ [FeatureGuard(typeof(RequiresDynamicCodeAttribute))]
+ [UnconditionalSuppressMessage("Trimming", "IL4000",
+ Justification = "Runtime assembly replacement is unavailable in fixed trimmed/AOT images.")]
+ internal static bool IsRuntimeVersionValidationSupported => true;
+
///
/// Guards the reflection-based authentication configuration and initializer loading path.
///
diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/PackageReadme.md b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/PackageReadme.md
index 26c5241deb..09d0ec8a02 100644
--- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/PackageReadme.md
+++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/PackageReadme.md
@@ -22,7 +22,7 @@ This package supports:
## Authentication registration
The provider registry and bootstrap live in this package. `GetProvider` and `SetProvider`
-work directly, including under NativeAOT, without loading SqlClient through reflection.
+work directly, including under NativeAOT, without a reflective registration bridge to SqlClient.
Configuration providers and configured initializers take precedence over application
registrations; application registrations take precedence over the discovered Azure default.
Registry initialization failures return `null` and `false`, respectively. Null providers,
@@ -48,6 +48,22 @@ each required authentication method before opening connections. The Azure provid
by `Microsoft.Data.SqlClient.Extensions.Azure`. Publish-time overrides use
`RuntimeHostConfigurationOption` with `Trim="true"`; runtime changes cannot affect trimming.
+## Package version policy
+
+Upgrade all installed SqlClient family packages to the same exact version, including
+prerelease labels: SqlClient, Abstractions, Azure, Logging and AKV. SqlServer.Server
+and native SNI are independently versioned.
+
+Untrimmed registry bootstrap compares the loaded SqlClient and discovered Azure assemblies'
+informational versions against Abstractions, excluding source-control build metadata.
+Mismatches are cached and traced with upgrade/restart guidance; `GetProvider` and
+`SetProvider` return `null` and `false`. Disabling configuration or Azure discovery
+does not disable the SqlClient runtime check.
+
+Trimmed/NativeAOT fixed images remove reflective runtime validation. Ensure the
+resolved package versions match before publishing; consumer build-time enforcement
+is maintained separately from authentication bootstrap. Mixed versions are not supported.
+
## Authentication configuration
The public `SqlAuthenticationProviderConfigurationSection` (legacy
diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderRegistry.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderRegistry.cs
index 771d839e38..6ec312f8ad 100644
--- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderRegistry.cs
+++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderRegistry.cs
@@ -43,7 +43,7 @@ internal sealed class SqlAuthenticationProviderRegistry
private ExceptionDispatchInfo? _bootstrapFailure;
///
- /// Runs the enabled configuration and Azure discovery steps on first access.
+ /// Runs the enabled version validation, configuration and Azure discovery steps on first access.
///
///
/// Reentrant initializer registrations can use the registry while bootstrap is in progress.
@@ -62,6 +62,10 @@ private void EnsureInitialized()
_initializing = true;
try
{
+ if (AuthenticationFeatureSwitches.IsRuntimeVersionValidationSupported)
+ {
+ ValidateSqlClientVersion();
+ }
if (AuthenticationFeatureSwitches.IsAppConfigSupported)
{
LoadConfiguration();
@@ -125,7 +129,7 @@ private void LoadConfiguration()
/// Discovers the optional Azure extension and registers its provider as the authentication default.
///
///
- /// Validates the extension's public key token in signed builds.
+ /// Validates the extension's family version and, in signed builds, its public key token.
/// Defaults do not replace existing registrations. Handled loading or construction failures
/// are logged and leave the application responsible for supplying a provider.
///
@@ -205,6 +209,7 @@ private void LoadAzureExtension()
return;
}
+ ValidateFamilyVersion(assembly);
SqlClientEventSource.Log.TryTraceEvent(
nameof(SqlAuthenticationProviderRegistry) +
": Azure extension assembly={0} found; " +
@@ -693,6 +698,53 @@ private bool Register(SqlAuthenticationMethod authenticationMethod,
return default;
}
+ /// Loads SqlClient and validates its exact family version against Abstractions.
+ /// A missing SqlClient assembly is logged and does not prevent registration.
+ /// The family versions differ or cannot be determined.
+ [RequiresUnreferencedCode("The SqlClient assembly is loaded by name for version validation.")]
+ [RequiresDynamicCode("The SqlClient assembly is loaded dynamically.")]
+ private static void ValidateSqlClientVersion()
+ {
+ Assembly assembly;
+ try
+ {
+ assembly = Assembly.Load("Microsoft.Data.SqlClient");
+ }
+ catch (FileNotFoundException e)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(
+ "SqlClient version validation skipped: assembly not installed. {0}", e.Message);
+ return;
+ }
+ ValidateFamilyVersion(assembly);
+ }
+
+ /// Enforces exact family-version agreement between an assembly and Abstractions.
+ /// The loaded SqlClient family assembly to validate.
+ /// The family versions differ or cannot be determined.
+ internal static void ValidateFamilyVersion(Assembly assembly)
+ {
+ string expected = GetFamilyVersion(typeof(SqlAuthenticationProvider).Assembly);
+ string actual = GetFamilyVersion(assembly);
+ if (!string.Equals(expected, actual, StringComparison.Ordinal))
+ {
+ throw new InvalidOperationException(
+ $"SqlClient family version mismatch: {assembly.GetName().Name} is {actual}, " +
+ $"but Microsoft.Data.SqlClient.Extensions.Abstractions is {expected}. " +
+ "Upgrade all SqlClient family packages together.");
+ }
+ }
+
+ /// Reads an assembly's informational version without build metadata.
+ /// The assembly whose family version is required.
+ /// The version including any prerelease label, but excluding the build metadata suffix.
+ /// The assembly has no informational version attribute.
+ private static string GetFamilyVersion(Assembly assembly) =>
+ assembly.GetCustomAttribute()?
+ .InformationalVersion.Split('+')[0]
+ ?? throw new InvalidOperationException(
+ $"Cannot determine the exact SqlClient family version of {assembly.GetName().Name}.");
+
/// Maps a configured authentication method name to its enum value, ignoring case.
/// The authentication method name from a configured provider entry.
/// The corresponding supported Active Directory authentication method.
diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/RuntimeFamilyVersionTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/RuntimeFamilyVersionTest.cs
new file mode 100644
index 0000000000..fcd183d525
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/RuntimeFamilyVersionTest.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test;
+
+/// Checks exact loaded-family versions independently of assembly binding versions.
+public class RuntimeFamilyVersionTest
+{
+ /// The owning assembly is always accepted as the expected family version.
+ [Fact]
+ public void MatchingRuntimeVersionSucceeds() =>
+ SqlAuthenticationProviderRegistry.ValidateFamilyVersion(typeof(SqlAuthenticationProvider).Assembly);
+
+ /// Source-control build metadata does not change an otherwise identical package version.
+ [Fact]
+ public void MatchingVersionWithDifferentBuildMetadataSucceeds()
+ {
+ string expected = typeof(SqlAuthenticationProvider).Assembly
+ .GetCustomAttribute()!.InformationalVersion.Split('+')[0];
+ SqlAuthenticationProviderRegistry.ValidateFamilyVersion(CreateAssembly(expected + "+different.commit"));
+ }
+
+ /// Different stable or prerelease versions fail with exact versions and upgrade guidance.
+ [Theory]
+ [InlineData("7.0.1")]
+ [InlineData("1.0.0")]
+ [InlineData("8.0.0-different-preview")]
+ public void MixedRuntimeVersionThrows(string actual)
+ {
+ InvalidOperationException error = Assert.Throws(
+ () => SqlAuthenticationProviderRegistry.ValidateFamilyVersion(CreateAssembly(actual)));
+ Assert.Contains("version mismatch", error.Message);
+ Assert.Contains(actual, error.Message);
+ Assert.Contains("Microsoft.Data.SqlClient.Extensions.Abstractions", error.Message);
+ Assert.Contains("Upgrade all SqlClient family packages together", error.Message);
+ }
+
+ /// Assemblies lacking version evidence are rejected rather than silently accepted.
+ [Fact]
+ public void MissingInformationalVersionThrows()
+ {
+ InvalidOperationException error = Assert.Throws(
+ () => SqlAuthenticationProviderRegistry.ValidateFamilyVersion(CreateAssembly(null)));
+ Assert.Contains("Cannot determine the exact SqlClient family version", error.Message);
+ }
+
+ /// Creates an in-memory assembly with controlled version metadata and no filesystem side effects.
+ private static Assembly CreateAssembly(string? version)
+ {
+ var assembly = AssemblyBuilder.DefineDynamicAssembly(
+ new AssemblyName("RuntimeVersionFixture"), AssemblyBuilderAccess.Run);
+ if (version is not null)
+ {
+ ConstructorInfo constructor = typeof(AssemblyInformationalVersionAttribute).GetConstructor([typeof(string)])!;
+ assembly.SetCustomAttribute(new CustomAttributeBuilder(constructor, [version]));
+ }
+ return assembly;
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/PackageReadme.md b/src/Microsoft.Data.SqlClient/src/PackageReadme.md
index d299d594a9..736aa69cd1 100644
--- a/src/Microsoft.Data.SqlClient/src/PackageReadme.md
+++ b/src/Microsoft.Data.SqlClient/src/PackageReadme.md
@@ -78,6 +78,15 @@ on first access. Failed initialization or registration returns `null`/`false` an
diagnostic guidance through `Microsoft.Data.SqlClient.EventSource` (Informational level,
Trace keyword `0x2`). Bootstrap failures are cached; correct the cause and restart the application.
+Upgrade SqlClient, Abstractions, Azure, Logging and AKV together to the same exact package
+version, including prerelease labels. Untrimmed bootstrap validates loaded SqlClient and
+discovered Azure informational versions against Abstractions; source-control build metadata
+is ignored. Disabling app.config
+or discovery does not disable the SqlClient runtime check. Fixed trimmed/AOT images remove
+reflective runtime validation, so ensure the resolved package versions match before
+publishing. Consumer build-time enforcement is maintained separately from authentication
+bootstrap. SqlServer.Server and native SNI remain independently versioned.
+
With this package reference, you can continue to use Entra ID authentication modes directly in your connection string:
```csharp
diff --git a/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/RuntimeVersionTest/RuntimeVersionTest.csproj b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/RuntimeVersionTest/RuntimeVersionTest.csproj
new file mode 100644
index 0000000000..9ae02a6a17
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/AuthenticationTests/RuntimeVersionTest/RuntimeVersionTest.csproj
@@ -0,0 +1,9 @@
+
+
+ Microsoft.Data.SqlClient
+ net462;net10.0
+ 7.0.1
+ $(SqlClientAssemblyVersion)
+ false
+
+