dotnet / dotnet/SqlClient

DllNotFoundException for Microsoft.Data.SqlClient.SqlConnection.Open on .NET8 preview 5 when used in COM component loaded in default ALC

Open
#2,083 1 comment 0 reactions 0 assignees View on GitHub
Area\Native SNI Repro Available :heavy_check_mark:
Dominant language
C#
Stars
989
Forks
340
Avg merge
4d 19h
Merged PRs (30d)
72

Description

### Describe the bug
When using Microsoft.Data.SqlClient in a COM component using a new runtime configuration option added in .NET 8.0 (preview 5 at this point), the DLL resolution fails to load the native SNI implementation.

The issue leading to the fix to allow loading COM component into default ACL is discussed in more detail here:
https://github.com/dotnet/runtime/issues/66013

And the pull request, which added the new runtime configuration switch to .NET 8.0 is here:
https://github.com/dotnet/runtime/pull/79026

Setting this option in a COM component, breaks the DLL resolution for the native component for some reason. Not exactly sure is this a bug in the .NET core runtime or a bug in the way the resolution is currently implemented in Microsoft.Data.SqlClient. I am reporting it here, in the hopes that someone with more insight into how the DLL resolution works, could take a look at what is happening in this scenario.

```
Exception message:
Stack trace:
Unhandled exception. System.DllNotFoundException: Unable to load DLL 'Microsoft.Data.SqlClient.SNI.dll' or one of its dependencies: The specified module could not be found. (0x8007007E)
at Microsoft.Data.SqlClient.SNINativeMethodWrapper.UnmanagedIsTokenRestricted(IntPtr token, Boolean& isRestricted)
at Microsoft.Data.Win32NativeMethods.IsTokenRestrictedWrapper(IntPtr token)
at Microsoft.Data.ProviderBase.DbConnectionPoolIdentity.GetCurrentNative()
at Microsoft.Data.ProviderBase.DbConnectionPoolIdentity.GetCurrent()
at Microsoft.Data.ProviderBase.DbConnectionPoolGroup.GetConnectionPool(DbConnectionFactory connectionFactory)
at Microsoft.Data.ProviderBase.DbConnectionFactory.GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup)
at Microsoft.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, SqlConnectionOverrides overrides)
at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides)
at Microsoft.Data.SqlClient.SqlConnection.Open()
at COMServer.Server.IServer.ComputePi() in COMServerDemo\COMServer\Server.cs:line 17
at IServer.ComputePi()
at COMClient.Program.Main(String[] args) in COMServerDemo\COMClient\Program.cs:line 12
```

### To reproduce
You can reproduce the problem by taking the code from COMServerDemo at:
https://github.com/dotnet/samples/tree/main/core/extensions/COMServerDemo

The code is described in more detail here: https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com

To reproduce the problem change "COMServer.csproj" to include a reference to Microsoft.Data.SqlClient:
```c#



```
And then change the code in "Server.cs" to following:
```c#
using System;
using System.Runtime.InteropServices;
using Microsoft.Data.SqlClient;

namespace COMServer
{
[ComVisible(true)]
[Guid(ContractGuids.ServerClass)]
public class Server : IServer
{
double IServer.ComputePi()
{
var connectionString = "Persist Security Info=False;Integrated Security=SSPI;server=(local)";
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
}
double sum = 0.0;
int sign = 1;
for (int i = 0; i < 1024; ++i)
{
sum += sign / (2.0 * i + 1.0);
sign *= -1;
}

return 4.0 * sum;
}
}
}
```
After the changes, compile the code using "dotnet.exe build /p:DefaultALC=True" and then register the created COM component using "regsvr32.exe
COMServerDemo\COMServer\bin\Debug\net8.0\COMServer.comhost.dll" (requires administrator priviledges). If you now run the created "COMClient\bin\Debug\net8.0\COMClient.exe" it will fail with the above exception.

### Expected behavior
I would except the DLL resolution for the native component to work like it does when the COM component is built without new runtime switch. Loading the COM component into the default ALC is required for interop scenarios using f.ex. WPF.

### Further technical details
Microsoft.Data.SqlClient version: 5.1.1
.NET target: .NET 8.0 preview 5
SQL Server version: SQL server 2019
Operating system: Windows 10 22H2

**Additional context**
I tested that turning on the work around mentioned in the documention, by setting "AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true);" makes the code work when it does not try to load the native SNI DLLs. Although it makes the code work, I feel like it's not a permanent solution as the documentation states that this feature is only intended for testing / debugging purposes.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.