4

Microsoft Azure Tech support has confirmed that all version of .Net 4.XX including 4.7.2 is supported in SQL Server Managed Instance CLR.

We're transforming some portion of Business layer with 4.7.2 and latest EF 6.2

Use Entity Framework in CLR Stored procedure

https://patrickdesjardins.com/blog/how-to-use-third-party-dll-reference-in-a-sql-clr-function

Therefore in order to leverage existing investment in well tested code we want to move certain business layer into DB.

However we ran into problems, when publishing.

Creating [System.Dynamic]...

Warning: The Microsoft .NET Framework assembly 'system.dynamic, version=4.0.0.0, culture=neutral, publickeytoken=b03f5f7f11d50a3a.'

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.

(47,1): SQL72014: .Net SqlClient Data Provider:

Msg 6218, Level 16, State 2, Line 1
CREATE ASSEMBLY for assembly 'System.Dynamic' failed because assembly 'System.Dynamic' failed verification. Check if the referenced assemblies are up-to-date and trusted (for external_access or unsafe) to execute in the database.
CLR Verifier error messages if any will follow this message [ :
System.Dynamic.ArgBuilder::MarshalToRef][mdToken=0x6000002][offset 0x00000000] Code size is zero. [ : System.Dynamic.ArgBuilder::UnmarshalFromRef][mdToken=0x6000003][offset

P.S. (IMHO, doesn't matter if EF core as well, neverthless we're trying this with EF6.2)

EDIT: Have given UNSAFE permission to all assemblies I'm referring to: Here are all the dependencies:

enter image description here

<ItemGroup>
    <Reference Include="Microsoft.CSharp">
      <HintPath>..\packages2019\dotnet 4.7.2\Microsoft.CSharp.dll</HintPath>
                <SqlPermissionSet>UNSAFE</SqlPermissionSet>
      <GenerateSqlClrDdl>True</GenerateSqlClrDdl>
      <IsModelAware>True</IsModelAware>
      <SpecificVersion>True</SpecificVersion>
    </Reference>
    <Reference Include="System.Dynamic">
      <HintPath>..\packages2019\dotnet 4.7.2\System.Dynamic.dll</HintPath>
                <SqlPermissionSet>UNSAFE</SqlPermissionSet>
      <GenerateSqlClrDdl>True</GenerateSqlClrDdl>
      <IsModelAware>True</IsModelAware>
      <SpecificVersion>True</SpecificVersion>
    </Reference>
    <Reference Include="System.Runtime.Serialization">
      <HintPath>..\packages2019\dotnet 4.7.2\System.Runtime.Serialization.dll</HintPath>
                <SqlPermissionSet>UNSAFE</SqlPermissionSet>
      <GenerateSqlClrDdl>True</GenerateSqlClrDdl>
      <IsModelAware>True</IsModelAware>
      <SpecificVersion>True</SpecificVersion>
    </Reference>
    <Reference Include="SMDiagnostics">
      <HintPath>..\packages2019\v4.0.30319\SMDiagnostics.dll</HintPath>
                <SqlPermissionSet>UNSAFE</SqlPermissionSet>
      <GenerateSqlClrDdl>True</GenerateSqlClrDdl>
      <IsModelAware>True</IsModelAware>
      <SpecificVersion>True</SpecificVersion>
    </Reference>
    <Reference Include="System.ServiceModel.Internals">
      <HintPath>..\packages2019\v4.0.30319\System.ServiceModel.Internals.dll</HintPath>
                <SqlPermissionSet>UNSAFE</SqlPermissionSet>
      <GenerateSqlClrDdl>True</GenerateSqlClrDdl>
      <IsModelAware>True</IsModelAware>
      <SpecificVersion>True</SpecificVersion>
    </Reference>
      <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
      <HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll</HintPath>
            <SqlPermissionSet>UNSAFE</SqlPermissionSet>
      <SpecificVersion>True</SpecificVersion>
      <IsModelAware>True</IsModelAware>
      <GenerateSqlClrDdl>True</GenerateSqlClrDdl>
    </Reference>
    <Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
      <HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll</HintPath>
            <SqlPermissionSet>UNSAFE</SqlPermissionSet>
      <SpecificVersion>True</SpecificVersion>
      <IsModelAware>True</IsModelAware>
      <GenerateSqlClrDdl>True</GenerateSqlClrDdl>
    </Reference>

EDIT 3: Unsafe code in Azure SQL Server MI enter image description here

EDIT 4:

  • The show-stopper is: System.RunTime.Serialization,
CREATE ASSEMBLY [System.Runtime.Serialization]
    AUTHORIZATION [dbo]
    FROM 0x4D5...
    WITH PERMISSION_SET = UNSAFE;

which prevents me to create Entity Framework UNSAFE assemblies into DB. Can we get past system.Runtime.Serialization?

GO
CREATE ASSEMBLY [EntityFramework]
    AUTHORIZATION [dbo]
    FROM 0x4D5A90...
    WITH PERMISSION_SET = UNSAFE;


GO
PRINT N'Creating [EntityFramework.SqlServer]...';


GO
CREATE ASSEMBLY [EntityFramework.SqlServer]
    AUTHORIZATION [dbo]
    FROM 0x4...
    WITH PERMISSION_SET = UNSAFE;

Warning: The Microsoft .NET Framework assembly 'system.runtime.serialization, version=4.0.0.0, culture=neutral, publickeytoken=b77a5c561934e089.' 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 6218, Level 16, State 2, Line 11 CREATE ASSEMBLY for assembly 'System.Runtime.Serialization' failed because assembly 'System.Runtime.Serialization' failed verification. Check if the referenced assemblies are up-to-date and trusted (for external_access or unsafe) to execute in the database. CLR Verifier error messages if any will follow this message [ : System.AppContextDefaultValues::PopulateDefaultValues][mdToken=0x6000001] [offset 0x00000000] Code size is zero.

Abhijeet
  • 13,562
  • 26
  • 94
  • 175
  • 1
    Well, tech support is not 100% correct, as certain assemblies from the .NET framework cannot be used in SQLCLR. I do not know if that is the case here; how are you creating the assembly using EF? – Niels Berglund Jun 30 '19 at 09:13
  • @NielsBerglund, thanks. downloaded EF into another project using Nuget & then inside Database project added reference to Entity Framework DLL – Abhijeet Jun 30 '19 at 09:24
  • Entity uses a SQL Server DLL and the DLL is different with different versions of SQL Server. So if you are working with a different version of SQL Server make sure you use Clean to force entire project to recompile with different versions of SQL Server. Also if you modify the database tables/columns make sure you update the mapping between the c# app and the database. – jdweng Jun 30 '19 at 10:21
  • @Abhijeet, what PERMISSION_SET do you give the assembly when creating it? – Niels Berglund Jun 30 '19 at 14:34
  • @NielsBerglund, Edited,, yes All UNSAFE, first trying to make it work then will secure it by creating the certificates. – Abhijeet Jun 30 '19 at 14:47
  • @Abhijeet. Ok, I would look at the first post you link to above, and take each of those assemblies: smdiagnostics.dll, etc., and create them as independent assemblies in the database (UNSAFE), and see what happens. I have a nagging suspicion that one or more of them cannot be deployed to SQL Server. – Niels Berglund Jun 30 '19 at 15:09
  • thanks @NielsBerglund pls. see edit-3 above. able to load them however system.runtime.serialization appears we can't create them on SQL MI – Abhijeet Jun 30 '19 at 16:34

2 Answers2

3

SQL Server's CLR host will use the highest version of the .NET Framework that is installed on the system, for the CLR version that it is linked to. SQL Server 2005 - 2008 R2 is linked to CLR version 2.0, hence those will use .NET Framework versions 2.0, 3.0, and 3.5. SQL Server 2012 and newer are all linked to CLR version 4.0, and hence will use .NET Framework versions 4.x.

That being said, some Framework libraries are built-in and do not need to be manually added. Those are listed here:

Supported .NET Framework Libraries

If you need a library that is not in that list, you can add it yourself, but that doesn't mean that you can add just any library. SQL Server only allows pure MSIL libraries, not mixed mode (mixed = contains both managed and unmanaged code). If you require a library that is mixed mode, then there is nothing you can do to load that into SQL Server. ALSO, please keep in mind that even if a library is pure MSIL today, that doesn't mean that it can't be converted to mixed mode in a future Framework update (and yes, this has happened).

With all of that in mind, the following item in the error message:

Code size is zero.

probably indicates that you are trying to load a reference library. You need to load the actual library, not the reference version of it.

I tried the following on SQL Server 2017 and was able to load them all, though not all needed to be loaded explicitly. Some automatically loaded others since they were in the same folder:

CREATE ASSEMBLY [Microsoft.CSharp]
FROM 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.CSharp.dll'
WITH PERMISSION_SET = UNSAFE;
-- includes System.Dynamic


CREATE ASSEMBLY [System.Runtime.Serialization]
FROM 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Runtime.Serialization.dll'
WITH PERMISSION_SET = UNSAFE;
-- includes SMDiagnostics, System.ServiceModel.Internals

Then check with:

SELECT * FROM sys.assemblies;
/*
Microsoft.CSharp
System.Dynamic
System.Runtime.Serialization
System.ServiceModel.Internals
SMDiagnostics
*/

Please note that, for the moment, I set the database to TRUSTWORTHY ON. This is not something that I generally recommend, and might not be necessary, but until I complete my post dealing with how to handle this properly and easily, TRUSTWORTHY will suffice.

I did not have the EntityFramework libraries to load, but the error you are getting is on the first one and the statements I posted above do not get that error. If you are able to execute those 2 statements in order to get all 5 libraries loaded, and then get an error on EntityFramework, then we will take a look at the specific error message.

HOWEVER, even if you are able to load all of these DLLs, including the two for EntityFramework, that is not a guarantee that you will actually be able to use EF. It is possible that EF is prevented from running in SQLCLR. I can't remember about EF specifically, but I do know that SMO, for example, includes code to detect if it is running within SQL Server, and if it is, then it will raise an exception stating that it is not allowed to run within SQL Server.

Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
  • thanks.. unfortunately I'm working on Azure SQL server managed instance that doesn't support loading the assembly from file. Entity Framework DLL requires corresponding system.runtime.. apparently we can't rewrite our version there. – Abhijeet Jun 30 '19 at 16:03
  • I might not have attempted to run EF on server, but this link comes from high reputation @david, from Microsoft... https://stackoverflow.com/a/48290693/1431250 – Abhijeet Jun 30 '19 at 16:06
  • 1
    Um, that answer was intended to persuade you _not_ to attempt to run EF in SQL CLR. – David Browne - Microsoft Jun 30 '19 at 16:13
  • @Abhijeet Ok, had forgotten about the restriction on using `FROM 'file'`, but the overall issue is the same: somehow you are not loading the correct form of the DLL. Try changing the source location for the Framework libraries to the path that I used, and keep the `..\packages\EntityFramework.6.2.0` path for the EF DLLs. Of course, I just noticed that the EF DLLs are for .NET 4.5 and you are trying to use 4.7.2 on the server, so _maybe_ that is part of the problem. Either way, you need to use the same .NET version for the Framework libraries that exist on the Azure server. – Solomon Rutzky Jun 30 '19 at 16:14
  • @Abhijeet Your "edit 3" does not really show anything. You mentioned (in comments, not in edit 3) not being able to load **System.Runtime.Serialization**. What is the error message? Did you make sure to load the actual DLL and not the reference assembly? I don't see the first two DLLs loaded: **Microsoft.CSharp** and **System.Dynamic**. You probably need to load those first. – Solomon Rutzky Jun 30 '19 at 16:40
  • I see it. Thanks. But it's the same error: "Code size is zero." Are you 100% sure that you are trying to load the correct version of the assembly? The DLL found in **C:\Windows\Microsoft.NET\Framework64\v4.0.30319** ? And what about loading those first two assemblies? – Solomon Rutzky Jun 30 '19 at 16:54
2

Microsoft Azure Tech support has confirmed that all version of .Net 4.XX including 4.7.2 is supported in SQL Server Managed Instance CLR

Managed Instance uses the latest version of .NET Framework for hosting SQL CLR assemblies. That doesn't mean it supports loading untested .NET Framework assemblies. See the support statement here.

Furthermore Managed Instance does not allow UNSAFE CLR assemblies, as these would enable you to run arbitrary code and access server resources directly.

Even if you could get all these .NET Framework assemblies loaded into your database, it would not be a supportable solution in Managed Instance. As the support statement above makes clear, you must keep the copies of the .NET framework assemblies loaded in your database in sync with the .NET framework version on the server. The .NET Framework on the server is updated in Windows updates. When you are managing the server keeping your assemblies in-sync with the version in Windows is hard enough. But when Microsoft is patching the server, you have no way to know that you need to update your assemblies.

When running on an Azure VM you will be able to install all of the assemblies that EF requires. The only restriction in a VM is that you can't load mixed-mode assemblies, but I don't think EF (currently) relies on any of those. If you go to production with a solution involving .NET Framework assemblies loaded into your database, you should probably implement a startup stored procedure or scheduled task that refreshes your database assemblies from the Windows .NET Framework folder every time SQL Server starts.

Anyway here's a powershell script I was able to get EF6 and its dependencies loaded into a SQL Server database. But remember, just because you can load an assembly, doesn't mean it's going to work correctly. You'll have to test extensively to determine if your EF code will actually work.

But running your .NET code on your SQL Server is unusual, and is usually a bad idea. It is very close to your data, but so is a separate VM on the same VNet. It makes your SQL Server harder to manage, and there are typically much easier ways to accomplish whatever you're hoping to gain by running your code on the SQL Server,

And, moreover, if you are running your code locally on the SQL Server, there's no good reason why it has to be SQL CLR, probably. You can just run your code in a Console App and trigger it with xp_cmdshell or a SQL Agent job.

Anyway here's the powershell for registering the assemblies:

$constr = "server=localhost;database=clrtest;integrated security=true"
$folder = "C:\Users\dbrowne\Source\Repos\SqlClrTest\ClassLibrary1\bin\Debug"
$netfx = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319"

$dlls = @(
    "system.dynamic",
    "microsoft.csharp",
    "system.componentmodel.dataannotations",
    "smdiagnostics",
    "system.servicemodel.internals",
    "system.runtime.serialization", 
    "entityframework",
    "entityframework.sqlserver",
    "YourClassLibrary",
    "YourSqlClrProject"
    )


[System.Data.SqlClient.SqlConnection]$con = New-Object System.Data.SqlClient.SqlConnection $constr
$con.Open()

[System.Data.SqlClient.SqlCommand] $cmd = $con.CreateCommand()
$cmd.CommandText = @"

    if cast(serverproperty('ProductMajorVersion') as int) >= 14
    begin


        DECLARE @hash varbinary(64);

        SELECT @hash = HASHBYTES('SHA2_512', @assemblyFile);

        declare @description nvarchar(4000) = @name

        if not exists (select * from sys.trusted_assemblies where hash = @hash)
        begin
          EXEC sys.sp_add_trusted_assembly @hash = @hash,
                                           @description = @description;
          print 'trusted assembly added'
        end

    end

   declare @sql nvarchar(max) 

   if exists (select * from sys.assemblies where name = @name)
   begin

        set @sql =  concat('
        alter assembly ',quotename(@name),'
        FROM @assemblyFile
        WITH PERMISSION_SET = UNSAFE;  
        ')
       EXECUTE sp_executesql @sql, N'@assemblyFile varbinary(max)', @assemblyFile = @assemblyFile;
       print 'updated assembly ' + @name
   end
   else
   begin

        set @sql =  concat('
        create assembly ',quotename(@name),'
        AUTHORIZATION [dbo]
        FROM @assemblyFile
        WITH PERMISSION_SET = UNSAFE;  
        ')

       EXECUTE sp_executesql @sql, N'@assemblyFile varbinary(max)', @assemblyFile = @assemblyFile;
       print 'added assembly ' + @name

   end


"@

$pName = $cmd.Parameters.Add("@name", [System.Data.SqlDbType]::NVarChar, 1000)
$pAssemblyFile = $cmd.Parameters.Add("@assemblyFile", [System.Data.SqlDbType]::VarBinary, -1)



foreach ($targetDll in $dlls)
{
    try
    {
       $pName.Value = $targetDll
       if ([System.IO.File]::Exists("$folder\$targetDll.dll"))
       {
          $pAssemblyFile.Value = [System.IO.File]::ReadAllBytes("$folder\$targetDll.dll")
       }
       else
       {
          $pAssemblyFile.Value = [System.IO.File]::ReadAllBytes("$netfx\$targetDll.dll")

       }

       $result = $cmd.ExecuteNonQuery()

    }
    catch [System.Data.SqlClient.SqlException]
    {
       [System.Data.SqlClient.SqlException] $ex = $_.Exception

       write-host "$($ex.Class) $($ex.Number) $($ex.Message) "

       write-host ""

       continue;
    }
}
$con.Close()
David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • Hi David thanks, need for the hour for me is to get Entity Framework up and running, then we will refactor our code. we have been able to load unsafe assemblies in Azure SQL CLR MI. what else are we missing pls. Please see edit-3 above. – Abhijeet Jun 30 '19 at 16:29
  • Even if you get some .NET Framework assemblies from _your_ computer installed in the database, they would still need to match the versions on the server, which you don't control. This will probably not work, and will certainly not be supported in SQL Managed Instance. – David Browne - Microsoft Jun 30 '19 at 17:42
  • Yeah thanks, therefore I will provision SQL server on a VM, rather, and disable update for a while. Do you foresee any problem from EF / LINQ side.. pls? – Abhijeet Jun 30 '19 at 18:28
  • Yes. This has never been tried, and there are lots of ways for it to _not work_. When you're running on a VM, you can simply compile your CLR code into a Console Application and invoke it with xp_cmdshell or a SQL Agent job. This is both supported, and will actually work. But, on a VM you are free to try this, and I'm adding a powershell script to help you out. – David Browne - Microsoft Jun 30 '19 at 18:47
  • thanks.. for another option.. IMHO, using Agent edition will be as good as moving Web Server & Database server into same VM. we're getting more inclined towards taking that path. (just to validate the latency) IMHO our Business layer will be faster in one machine -- than two separate servers in same region. – Abhijeet Jul 01 '19 at 09:56
  • Yes. Running in IIS on the same Server is much more manageable than SQL CLR or Agent. But remember than the network is only one component of latency between your App code and the database server. Copying data back and forth is still not free. And commiting transactions can contribute too. Deploying the app to a single server might help, but improving application performance usually requires changing the application code. – David Browne - Microsoft Jul 01 '19 at 11:57