Description
This partially comes from the netfx/netcore project merge, but has opened into a more general question: is SqlClient's .NET Framebuild build supported for use in SQLCLR scenarios? My experience so far has been that it isn't working, but some review of the code suggests to me that it can't work.
Background context
I'm trying to test the context connection. When Context Connection is true, SqlClient starts to use a lot of the *Smi
and Smi*
classes which are in the .NET Framework project, plus various branches within other classes. My library is the foundation of a testbed to prove that the functionality works: first with .NET Framework's intrinsic System.Data.SqlClient, second with Microsoft.Data.SqlClient as-is, and third after any code merges.
System.Data.SqlClient
The sample library which I've used as a testbed is pretty simple. It contains one class, as below:
public class NetFxStoredProcedures
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void uspEcho(System.Data.SqlTypes.SqlString spToEcho)
{
var record = new Microsoft.SqlServer.Server.SqlDataRecord(
new Microsoft.SqlServer.Server.SqlMetaData("Echo", System.Data.SqlDbType.NVarChar, 200)
);
record.SetSqlString(0, spToEcho);
Microsoft.SqlServer.Server.SqlContext.Pipe.Send(record);
}
[Microsoft.SqlServer.Server.SqlProcedure]
public static void uspPrintDate()
{
using (var cConn = new System.Data.SqlClient.SqlConnection("Context Connection=true"))
{
cConn.Open();
var cmd = new System.Data.SqlClient.SqlCommand("select getdate() as MyDate", cConn);
DateTime dt = (DateTime)cmd.ExecuteScalar();
Microsoft.SqlServer.Server.SqlContext.Pipe.Send("The current date is: " + dt.ToString("O"));
}
}
}
I then load it into SQL Server with the script:
use [master]
create database SqlClrTestDb
alter database SqlClrTestDb
set trustworthy on
go
use [SqlClrTestDb]
create assembly NetFx from '<path>\SqlClrTestbed.dll'
with permission_set = unsafe
go
create procedure usp_NetFxEcho
@spToEcho nvarchar(200)
as external name NetFx.[SqlClrTestbed.NetFxStoredProcedures].uspEcho
go
create procedure usp_NetFxPrint
as external name NetFx.[SqlClrTestbed.NetFxStoredProcedures].uspPrintDate
go
exec usp_NetFxEcho 'Hello, world!'
exec usp_NetFxPrint
go
drop procedure usp_NetFxEcho
drop procedure usp_NetFxPrint
drop assembly NetFx
go
use [master]
drop database SqlClrTestDb
go
This tests three situations:
- SQL Server can load the DLL at all
uspEcho
allows me to issue a result set back to the calleruspPrintDate
can make use of the Context Connection connection string parameter and run a query
It works as expected.
Microsoft.Data.SqlClient
Microsoft.Data.SqlClient has a few problems. I've adapted the class as below:
public class MdsStoredProcedures
{
/*[Microsoft.SqlServer.Server.SqlProcedure]
public static void uspEcho(System.Data.SqlTypes.SqlString spToEcho)
{
var record = new Microsoft.Data.SqlClient.Server.SqlDataRecord(
new Microsoft.Data.SqlClient.Server.SqlMetaData("Echo", System.Data.SqlDbType.NVarChar, 200)
);
record.SetSqlString(0, spToEcho);
Microsoft.SqlServer.Server.SqlContext.Pipe.Send(record);
}*/
[Microsoft.SqlServer.Server.SqlProcedure]
public static void uspPrintDate()
{
using (var cConn = new Microsoft.Data.SqlClient.SqlConnection("Context Connection=true"))
{
cConn.Open();
using (var cmd = new Microsoft.Data.SqlClient.SqlCommand("select getdate() as MyDate", cConn))
{
DateTime dt = (DateTime)cmd.ExecuteScalar();
Microsoft.SqlServer.Server.SqlContext.Pipe.Send("The current date is: " + dt.ToString("O"));
}
}
}
}
uspEcho
is laid out as per the documentation here. The primary problem for this method is that there's no Microsoft.Data.SqlClient.Server.SqlContext class (or its supporting classes) and thus, no way to return result sets. I did try to use Microsoft.SqlServer.Server.SqlContext
, but there's no type forwarding from Microsoft.SqlServer.Server.SqlDataRecord
to Microsoft.Data.SqlClient.Server.SqlDataRecord
. I commented it out to avoid the compilation error.
uspPrintDate
was going to be a bit more important, since it exercises the context connection. This is almost identical to the System.Data.SqlClient classes, and it compiles - I don't think there's much to look at here.
Failure
Once compiled, I can't load the library which uses Microsoft.Data.SqlClient into SQL Server. I'm using this SQL script:
use [master]
create database SqlClrTestDb
alter database SqlClrTestDb
set trustworthy on
go
use [SqlClrTestDb]
create assembly NetFx from '<path>\SqlClrTestbed.dll'
with permission_set = unsafe
go
create procedure usp_MdsPrint
as external name NetFx.[SqlClrTestbed.MdsStoredProcedures].uspPrintDate
go
exec usp_MdsPrint
go
drop procedure usp_MdsPrint
drop assembly NetFx
go
use [master]
drop database SqlClrTestDb
go
This behaves unexpectedly; I'm unable to create the assembly as a result of SQL Server trying to load two versions of System.Runtime.CompilerServices.Unsafe. The output I receive is:
Warning: The Microsoft .NET Framework assembly 'system.numerics.vectors, version=4.1.4.0, culture=neutral, publickeytoken=b03f5f7f11d50a3a, processorarchitecture=msil.' you are registering is not fully tested in the SQL Server hosted environment and is not supported. In the future, if you upgrade or service this assembly or the .NET Framework, your CLR integration routine may stop working. Please refer SQL Server Books Online for more details.
Warning: The Microsoft .NET Framework assembly 'system.runtime.compilerservices.unsafe, version=6.0.0.0, culture=neutral, publickeytoken=b03f5f7f11d50a3a, processorarchitecture=msil.' you are registering is not fully tested in the SQL Server hosted environment and is not supported. In the future, if you upgrade or service this assembly or the .NET Framework, your CLR integration routine may stop working. Please refer SQL Server Books Online for more details.
Msg 10300, Level 16, State 1, Line 68
Assembly 'System.Memory' references assembly 'system.runtime.compilerservices.unsafe, version=4.0.4.1, culture=neutral, publickeytoken=b03f5f7f11d50a3a.', which is not present in the current database. SQL Server attempted to locate and automatically load the referenced assembly from the same location where referring assembly came from, but that operation has failed (reason: version, culture or public key mismatch). Please load the referenced assembly into the current database and retry your request.
Interestingly, this occurs even with a binding redirect in sqlservr.exe.config, excerpted below. I'm a little suspicious that SQL Server's assembly load rules might be ignoring this though.
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
System.Runtime.CompilerServices.Unsafe v4.0.4.1 corresponds to the NuGet package version 4.5.3. I'm not sure why it's binding to that version of the package - all of the direct and transitive dependencies require at least version 6.0.0.0, and I've walked through the references of every built DLL and found no other version.
I located the v4.0.4.1 System.Runtime.CompilerServices.Unsafe DLLs and tried adding them as assemblies manually, but SQL Server won't allow two versions of the same assembly to be loaded. I thus can't load my testbed DLL to check whether context connections work.
Speculative failure
I'm trying to test context connections because I'm not sure they can actually work. The core of the context connection logic comes from SmiContextFactory
. In the constructor, we see the Microsoft.SqlServer.Server.InProcLink.Instance
field is converted to a Microsoft.Data.SqlClient.Server.SmiLink
object, then the result of SmiLink.GetCurrentContext
is passed a Microsoft.Data.SqlClient.Server.SmiEventSink
instance and its return value is converted to a Microsoft.Data.SqlClient.Server.SmiContext
object. However, when I look at the SqlAccess assembly which is shipped as part of SQL Server, all of these types are in the Microsoft.SqlServer.Server
namespace in System.Data; I think the type conversion will fail, leaving the connection unable to open.
Issue/question
I've raised this as an issue because adding Microsoft.Data.SqlClient to a clean .NET Framework project results in SQL Server being unable to load the resultant DLL. My broader question is whether SQLCLR usage is supported in SqlClient's .NET Framework build - both for context connections, and for returning result sets.
If M.D.S' .NET Framework build is supposed to support SQLCLR usage, does a working sample environment exist? I didn't see anything in the test projects. If it's not supposed to support SQLCLR, I'll close this issue with a couple of PRs to make sure that the Context Connection connection string parameter throws and to remove the underlying classes from the .NET Framework library.