-->

Pass table as parameter to SQLCLR TV-UDF

2020-08-16 03:39发布

问题:

We have a third-party DLL that can operate on a DataTable of source information and generate some useful values, and we're trying to hook it up through SQLCLR to be callable as a table-valued UDF in SQL Server 2008.

Taking the concept here one step further, I would like to program a CLR Table-Valued Function that operates on a table of source data from the DB.

I'm pretty sure I understand what needs to happen on the T-SQL side of things; but, what should the method signature look like in the .NET (C#) code? What would be the parameter datatype for "table data from SQL Server?"

e.g.

/* Setup */
CREATE TYPE InTableType 
AS TABLE (LocationName VARCHAR(50), Lat FLOAT, Lon FLOAT)
GO 

CREATE TYPE OutTableType 
AS TABLE (LocationName VARCHAR(50), NeighborName VARCHAR(50), Distance FLOAT)
GO

CREATE ASSEMBLY myCLRAssembly 
FROM 'D:\assemblies\myCLR_UDFs.dll' 
WITH PERMISSION_SET = EXTERNAL_ACCESS
GO
CREATE FUNCTION GetDistances(@locations InTableType)
RETURNS OutTableType
AS 
EXTERNAL NAME myCLRAssembly.GeoDistance.SQLCLRInitMethod
GO

/* Execution */

DECLARE @myTable InTableType
INSERT INTO @myTable(LocationName, Lat, Lon) VALUES('aaa', -50.0, -20.0)
INSERT INTO @myTable(LocationName, Lat, Lon) VALUES('bbb', -20.0, -50.0)
SELECT * FROM @myTable

DECLARE @myResult OutTableType
INSERT INTO @myResult
GetDistances @myTable /* SQLCLR Call: GeoDistance.SQLCLRInitMethod(@myTable) */

The lat/lon -> distance thing is a silly example that should of course be better handled entirely in SQL; but I hope it illustrates the general intent of table-in -> table-out through a table-valued UDF tied to a SQLCLR assembly.

I am not certain this is possible; what would the SQLCLRInitMethod method signature look like in the C#?

public class GeoDistance
{
    [SqlFunction(FillRowMethodName = "FillRow")]
    public static IEnumerable SQLCLRInitMethod(<appropriateType> myInputData)
    {
      //...
    }

    public static void FillRow(...)
    {
      //...
    }
}

If it's not possible, I know I can use a "context connection=true" SQL connection within the C# code to have the CLR component query for the necessary data given the relevant keys; but that's sensitive to changes in the DB schema. So I hope to just have SQL bundle up all the source data and pass it to the function.

Bonus question - assuming this works at all, would it also work with more than one input table?

回答1:

Turns out that there's a fixed list of valid inputs on a SQLCLR function, determined by the available mapping between .NET datatypes and SQL datatypes

SQL Datatype "table" is explicitly called out as having no mapping through the CLR.

Ergo, it's not possible to pass table-valued data INTO a table-valued CLR function as method parameters.

Alternatives

It does seem possible to get tabular data in via select ... for xml contortions to feed into a SqlXml parameter.

I have successfully used SqlConnection conn = new SqlConnection("context connection = true"); in the .NET code to let the TVF query the DB for the tabular data it needs.



回答2:

This question seems to be (mostly) a duplicate of:

CLR Table-valued function with array argument

As a quick note, in that question I recommended: delimited list, XML, or CLR UDT.

There is also the option of filling a table and loading the DataTable from it in the function. Using a real Table is likely not recommended as it would require extra effort to make it "thread safe" (to not cross data with other SPIDs) and would require an additional clean-up process since the Function would not be able to do a DML statement to clean it up once it was done with the data. In certain situations maybe this is preferred but probably not for this particular case. Fortunately, Temporary Tables are accessible within SQLCLR Functions (as read-only, but they are not accessible at all in T-SQL functions). Using Temp Tables would have the same advantages as using permanent Tables but not the disadvantages of collisions with other SPIDs or needing to be cleaned up separately. The only requirement is that you use the Context Connection as that is the only way to access session-based objects (i.e. Temp Tables).

So for this particular case, I would recommend trying either the Temp Table or XML options.