Compatibility Issues with SQLClient Libraries After Migrating to .NET 6 from .NET Framework 4.8

Nova, Sirene R 0 Reputation points
2024-04-17T19:00:39.4733333+00:00

I've encountered issues with Microsoft sqlclient packages after upgrading a set of C# projects from .NET Framework 4.8 to .NET 6.0. The setup consists of a primary C# calculator application, a C# bridge for external integration, and a C++/CLI wrapper. The C# bridge utilizes a sqlclient package (System.Data.SqlClient in .NET Framework 4.8, Microsoft.Data.SqlClient in .NET 6.0) to import data from a SQL database.

All C# projects have been updated to .NET 6. The C++/CLI Runtime support has also been updated to .NET 6.

The challenge arises with the C++/CLI wrapper when it calls into the C# bridge that depends on either the System.Data.SqlClient or Microsoft.Data.SqlClient package. This call fails specifically when the database client library is used. Without these packages, the call works correctly.

The exception thrown is as follows:

Managed exception caught in build_calculator: Could not load file or assembly 'Microsoft.Data.SqlClient, Version=5.0.0.0, Culture=neutral, PublicKeyToken=23ec7fc2d6eaa4a5'. The system cannot find the file specified.

Note that the actual version is 5.2.0.0, but the error references an incorrect version number, which seems to be the assemblyVersion specified in the .deps.json file.

Here is a summary of the issue and the steps I have taken to resolve it:

  1. Upgraded DataClient: Upgraded System.Data.SqlClient. Transitioned to Microsoft.Data.SqlClient for compatibility with .NET 6.0.
  2. Removed Database Connection: Verified that the C++/CLI wrapper successfully calls the C# bridge when the sqlclient library is not invoked.
  3. App.config Binding Redirects: Implemented binding redirects in an attempt to resolve version conflicts. The redirects had no discernible effect, even though the configuration files were generated properly. Attempted this with both System.Data.SqlClient and Microsoft.Data.SqlClient.
  4. Direct DLL References: Extracted the DLLs and dependencies directly from the NuGet packages and referenced them in the project, which led to various other issues. Attempted this with both System.Data.SqlClient and Microsoft.Data.SqlClient.
  5. Explicit Assembly Loading: Tried to load the assembly explicitly within the C++/CLI project using Assembly::Load, ensuring the correct package version was specified. However, the correct file could not be located, and the error persisted. Attempted this with both System.Data.SqlClient and Microsoft.Data.SqlClient.

In the 5th/last step above the correct package version was searched for, but still couldn't be found. Here is the exception thrown for Microsoft.Data.SqlClient:

Error loading assembly: Could not load file or assembly 'Microsoft.Data.SqlClient, Version=5.2.0.0, Culture=neutral, PublicKeyToken=23ec7fc2d6eaa4a5'. The system cannot find the file specified.

I'm hoping someone in the community, or at Microsoft, could shed some light on this problem. Has anyone else faced a similar issue? Are there known workarounds or solutions to ensure that the correct versions of the SQL client libraries are found and loaded when using a C++/CLI project in .NET 6.0?

Here is a relevant section of the .deps.json file:

"Microsoft.Data.SqlClient/5.2.0": {
        "dependencies": {
          "Azure.Identity": "1.10.3",
          "Microsoft.Data.SqlClient.SNI.runtime": "5.2.0",
          "Microsoft.Identity.Client": "4.56.0",
          "Microsoft.IdentityModel.JsonWebTokens": "6.35.0",
          "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.35.0",
          "Microsoft.SqlServer.Server": "1.0.0",
          "System.Configuration.ConfigurationManager": "6.0.1",
          "System.Runtime.Caching": "6.0.0"
        },
        "runtime": {
          "lib/net6.0/Microsoft.Data.SqlClient.dll": {
            "assemblyVersion": "5.0.0.0",
            "fileVersion": "5.20.24059.2"
          }

Here is the section of C++/CLI code throwing exceptions:

#include "stdafx.h"
#include "CLIWrapper.h"
#include <msclr/marshal.h>
using namespace CSharpProjBridge;
using namespace msclr::interop;
using namespace System;
#include <cliext/vector>
#include <msclr\auto_gcroot.h>
using namespace System::Reflection;
 
namespace CLIWrapper {
	bool Wrapper::call_calculator(double* l_, double* s_, double t_, int arraySize)
	{
		try
		{
			// Dynamically load the assembly
			Assembly^ myAssembly = Assembly::Load("Microsoft.Data.SqlClient, Version=5.2.0.0, Culture=neutral, PublicKeyToken=23ec7fc2d6eaa4a5");
			Console::WriteLine("Assembly loaded successfully.");
		}
		catch (Exception^ e)
		{
			Console::WriteLine("Error loading assembly: " + e->Message);
		}
 
		try {
			array<double>^ l_Array = gcnew array<double>(arraySize);
			array<double>^ s_Array = gcnew array<double>(arraySize);
			// Copies C++ pointer arrays to useable form for CSharpProjInterface
			Runtime::InteropServices::Marshal::Copy(IntPtr((void*)l_), l_Array, 0, arraySize);
			Runtime::InteropServices::Marshal::Copy(IntPtr((void*)s_), s_Array, 0, arraySize);
			bool success = CSharpProjInterface::CallCalculator(l_Array, s_Array, t_);
			// Copies results from CSharpProj back to C++ pointer arrays
			Runtime::InteropServices::Marshal::Copy(l_Array, 0, IntPtr((void*)l_), 191);
			Runtime::InteropServices::Marshal::Copy(s_Array, 0, IntPtr((void*)s_), 191);
			return success;
		}
		catch (System::Exception^ ex) {
			System::Console::WriteLine("Managed exception caught in call_calculator: {0}", ex->Message);
		}
	}
 
	void Wrapper::build_calculator(char** components, char* parameterSet, int arraySize)
	{
		try
		{
			// Dynamically load the assembly
			Assembly^ myAssembly = Assembly::Load("Microsoft.Data.SqlClient, Version=5.2.0.0, Culture=neutral, PublicKeyToken=23ec7fc2d6eaa4a5");
			Console::WriteLine("Assembly loaded successfully.");
		}
		catch (Exception^ e)
		{
			Console::WriteLine("Error loading assembly: " + e->Message);
		}
 
		try {
			// Assuming BuildCalculator is a method within your managed code
			array<String^>^ compArray = gcnew array<String^, 1>(arraySize);
			for (int i = 0; i < arraySize; i++)
				// Copies components array from C++ pointer to usable form for CSharpProjInterface
				compArray[i] = marshal_as<String^>(components[i]);
			String^ dataSet = marshal_as<String^>(parameterSet);
			CSharpProjInterface::BuildCalculator(compArray, dataSet);
		}
		catch (System::Exception^ ex) {
			System::Console::WriteLine("Managed exception caught in build_calculator: {0}", ex->Message);
		}
 
	}
}
.NET CLI
.NET CLI
A cross-platform toolchain for developing, building, running, and publishing .NET applications.
322 questions
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,530 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 56,286 Reputation points
    2024-04-17T20:42:18.16+00:00

    loading .net core dll is different from 4.* assemblies. .net core hosting is not built into the o/s like the old framework. so your c/c++ needs to host .net core code, then use a different api to get entry points. see docs:

    https://learn.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting

    note: MS wanted to change the sqlclient namespace. so Microsoft.Data.SqlClient was originally a straight port of System.Data.SqlClient with only the namespace change. then new features are only added to Microsoft.Data.SqlClient,

    0 comments No comments