Skip to content

Microsoft.Data.SqlClient 7.0.1 cannot construct Extensions.Azure 7.0.2's ActiveDirectoryAuthenticationProvider (AmbiguousMatchException on new Options constructor) #4492

Description

@ricohomewood

Describe the bug

Microsoft.Data.SqlClient.Extensions.Azure 7.0.2 added a new
ActiveDirectoryAuthenticationProvider(ActiveDirectoryAuthenticationProviderOptions)
constructor. Microsoft.Data.SqlClient 7.0.1 cannot construct that type any more, so pairing
SqlClient 7.0.1 with Extensions.Azure 7.0.2 breaks all Microsoft Entra authentication.

Depending on the host, this surfaces in one of two ways:

  • No Active Directory provider is installedSqlAuthenticationProvider.GetProvider(...)
    returns null and connections using Entra authentication fail. Observed on Windows 11,
    framework-dependent.
  • TypeInitializationException escapes the static constructor and every subsequent use of the
    type fails. Observed on linux-x64 with a self-contained single-file publish (an EF Core migrations
    bundle).

In 7.0.1 the manager instantiates the provider unconditionally:

// SqlAuthenticationProviderManager.cs (v7.0.1)
var instance = Activator.CreateInstance(
    type,
    [Instance._applicationClientId])
    as SqlAuthenticationProvider;

When no applicationClientId is configured, that argument is null. A null argument is
assignable to both String and ActiveDirectoryAuthenticationProviderOptions, so the binder finds
two equally good candidates and throws AmbiguousMatchException.

The catch filter in 7.0.1 does not list AmbiguousMatchException, so it escapes the static
constructor. 7.0.2 fixed both halves of this (it selects the constructor explicitly and adds
AmbiguousMatchException to the filter), but nothing prevents the mismatched combination from being
resolved by NuGet — see "Further technical details" below for why this is easy to hit accidentally.

Exception message:
System.TypeInitializationException: The type initializer for
'Microsoft.Data.SqlClient.SqlAuthenticationProviderManager' threw an exception.
 ---> System.Reflection.AmbiguousMatchException: Ambiguous match found for
      'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider Void .ctor(System.String)'.

Stack trace:
   at System.DefaultBinder.BindToMethod(BindingFlags bindingAttr, MethodBase[] match, Object[]& args, ParameterModifier[] modifiers, CultureInfo cultureInfo, String[] names, Object& state)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at Microsoft.Data.SqlClient.SqlAuthenticationProviderManager..cctor()
   --- End of inner exception stack trace ---
   at Microsoft.Data.SqlClient.SqlAuthenticationProviderManager.GetProvider(SqlAuthenticationMethod authenticationMethod)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
   at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(...)
   at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides)

To reproduce

Complete, self-contained console project. No server is required — the exception happens while the
provider is being constructed.

Repro.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.1" />
    <PackageReference Include="Microsoft.Data.SqlClient.Extensions.Azure" Version="7.0.2" />
  </ItemGroup>
</Project>

Program.cs:

using System.Reflection;
using Microsoft.Data.SqlClient;

var extensions = Assembly.Load("Microsoft.Data.SqlClient.Extensions.Azure");
var providerType = extensions.GetType("Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider")!;

Console.WriteLine("Constructors:");
foreach (var ctor in providerType.GetConstructors())
{
    Console.WriteLine("  (" + string.Join(", ", ctor.GetParameters().Select(p => p.ParameterType.Name)) + ")");
}

// Exactly what SqlAuthenticationProviderManager's static constructor does in 7.0.1
// when no applicationClientId has been configured.
try
{
    var instance = Activator.CreateInstance(providerType, new object?[] { null });
    Console.WriteLine("Created: " + instance?.GetType().Name);
}
catch (Exception ex)
{
    Console.WriteLine(ex.GetType().Name + ": " + ex.Message);
}

Output:

Constructors:
  ()
  (String)
  (Func`2, String)
  (ActiveDirectoryAuthenticationProviderOptions)
AmbiguousMatchException: Ambiguous match found for
'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider Void .ctor(System.String)'.

To see the real-world symptom instead, keep the same package versions and open a connection using
Authentication=Active Directory Default. The connection fails with the TypeInitializationException
above rather than an authentication error.

Expected behavior

One of the following:

  1. Microsoft.Data.SqlClient.Extensions.Azure declares a lower bound on Microsoft.Data.SqlClient
    (or the reverse), so NuGet cannot resolve an extension version newer than the core library that
    understands it. Today neither package constrains the other — Extensions.Azure only depends on
    Microsoft.Data.SqlClient.Extensions.Abstractions — so the mismatch resolves silently.
  2. Failing that, a clear, actionable exception naming the version mismatch, instead of a
    TypeInitializationException wrapping a reflection binder error.

Adding the new (Options) constructor is source and binary compatible, but it is not compatible
with how older versions of the core library reflectively construct the type, so in practice it is a
breaking change for any already-resolved graph.

Further technical details

Microsoft.Data.SqlClient version: 7.0.1 (broken with Extensions.Azure 7.0.2); 7.0.2 is fine
Microsoft.Data.SqlClient.Extensions.Azure version: 7.0.2 (1.0.0 is fine with 7.0.1)
.NET target: .NET 10.0
SQL Server version: Azure SQL Database
Operating system: Ubuntu 24.04 (CI runner) and Windows 11

Constructor comparison:

Extensions.Azure version Constructors on ActiveDirectoryAuthenticationProvider
1.0.0 (), (String), (Func<DeviceCodeResult, Task>, String)
7.0.2 (), (String), (Func<DeviceCodeResult, Task>, String), (ActiveDirectoryAuthenticationProviderOptions)
7.1.0-preview2 same as 7.0.2

Version matrix observed:

Microsoft.Data.SqlClient Extensions.Azure Result
7.0.1 1.0.0 works
7.0.1 7.0.2 TypeInitializationException
7.0.2 7.0.2 works

Additional context

This is easy to hit unintentionally. An application only needs to reference
Microsoft.Data.SqlClient.Extensions.Azure explicitly (as the migration guidance instructs) while
picking up Microsoft.Data.SqlClient transitively. Microsoft.EntityFrameworkCore.SqlServer 10.0.10
requires Microsoft.Data.SqlClient 7.0.1, so a routine automated bump of the extension package to
7.0.2 silently produces the broken 7.0.1 + 7.0.2 pair, with no build warning.

The failure surfaced only at runtime, on the first connection using Microsoft Entra authentication.

Possibly related 7.0.2 changes around the new options/broker constructor: #4414, #4426.

Workaround

Reference Microsoft.Data.SqlClient explicitly and keep it version-aligned with
Microsoft.Data.SqlClient.Extensions.Azure:

<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
<PackageReference Include="Microsoft.Data.SqlClient.Extensions.Azure" Version="7.0.2" />

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
Needs Response

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions