How to integrate a .NET CLR UDF into SQL Server

In this tutorial I will demonstrate how to create a CLR User Defined Function (UDF) in Visual Studio, and then integrate it into SQL Server.

Why do I need a CLR User Defined Function?

Many SQL Server professionals have recently proved that performing certain operations in SQL using a CLR function rather than the built-in SQL functions provides massive performance gains.  One such example that caught my attenion was Adam Machanic's String Splitter.   This marvelous piece of code takes a comma-separated list of numbers and puts them into a table.

In my personal development, splitting strings was never an issue until recently we won a large contract for a customer that had hundreds of thousands of rows of data (in string format) that needed inserting into a SQL table.  Using our existing TSQL-only method, our server crawled to nearly stopping point trying to process this data...

SQL-only Functions can be Slow

The following T-SQL code creates a function which in turn creates a table variable, and inserts 100,001 rows of data by splitting a string into numeric values:

CREATE FUNCTION myfn_StringSplitter
    (    
        @separator char(1),
        @IdString nvarchar(max)
    )
RETURNS @tmp TABLE (ID int)
AS
    BEGIN

        DECLARE @separator_position int
        DECLARE @array_value nvarchar(max)            

        WHILE patindex('%' + @separator + '%' , @IdString) <> 0
            BEGIN
                select @separator_position =  patindex('%' + @separator + '%' , @IdString)
                select @array_value = left(@IdString, @separator_position - 1)
                INSERT INTO @tmp (ID) VALUES (@array_value)
                SELECT @IdString = stuff(@IdString, 1, @separator_position, '')
            END    
        
        RETURN            
    END
GO

SELECT * FROM dbo.myfn_StringSplitter(',', '1101, 3645, 4894, 9182, 3617, 2427 ...... removed for brevity ...... 5213, 9197, 4758, 4728, 9665, 9120, 991, 2126,') 

 Running the query on my development machine took 278.61 seconds:

 

How to Create a CLR User Defined Function in Visual Studio

I will now demonstrate how to build a CLR function in Visual Studio that can achieve the above in record time.

Start a new project of type SQL CLR Database Project:

Delete the files that Visual Studio adds automatically, and then add a new item, of type User-Defined Function:

Now paste this code over the top of the existing code (or write your own function obviously).  Credit here again goes to Adam Machanic, of which I slightly modified his excellent version to allow NULL values to be created from empty strings:

Imports System.Data
Imports Microsoft.SqlServer.Server
Imports System.Data.SqlTypes
Imports System.Data.SqlClient
Imports System.Collections
Imports System.Runtime.InteropServices

Public Class MyStringSplitter

    Private Class DoSplit
        Implements IEnumerator

        Private lastPos As Integer
        Private nextPos As Integer
        Private theString As String
        Private delimiter As Char

        Public Sub New(s As String, d As Char)
            Me.theString = s
            Me.delimiter = d
            Me.lastPos = -1
            Me.nextPos = -1
        End Sub

        Public ReadOnly Property Current() As Object Implements IEnumerator.Current
            Get
                Return theString.Substring(lastPos, nextPos - lastPos).Trim()
            End Get
        End Property

        Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
            If nextPos >= theString.Length Then
                Return False
            Else
                lastPos = nextPos + 1
                If lastPos = theString.Length Then
                    Return False
                End If
                nextPos = theString.IndexOf(delimiter, lastPos)
                If nextPos = -1 Then
                    nextPos = theString.Length
                End If
                Return True
            End If
        End Function

        Public Sub Reset() Implements IEnumerator.Reset
            Me.lastPos = -1
            Me.nextPos = -1
        End Sub

    End Class

    <Microsoft.SqlServer.Server.SqlFunction(FillRowMethodName:="FillRow", TableDefinition:="IdVal INT")> _
    Public Shared Function SplitToIntegers(instr As SqlString, <SqlFacet(IsFixedLength:=True, MaxSize:=1)> delimiter As SqlChars) As IEnumerator
        Return New DoSplit(instr.Value, Convert.ToChar(delimiter.Value))
    End Function

    Public Shared Sub FillRow(obj As Object, <Out()> ByRef IdVal As SqlInt32)
        If obj.ToString = String.Empty Then
            IdVal = SqlTypes.SqlInt32.Null
        Else
            IdVal = New SqlInt32(obj)
        End If
    End Sub

End Class

 Now build the function into a DLL file:

Make a note of where the DLL has been created, for example:

C:\Users\Dave\Documents\visual studio 2010\Projects\MyStringSplitter\bin\Debug\MyStringSplitter.dll

Integrating a CLR UDF into SQL Server

Configure SQL to accept CLR UDF's

Fire up SQL Management Studio and run this code which will allow us to add new CLR functions into our database:

USE MyDatabase
GO
sp_configure 'clr enabled', 1 
GO 

-- Output: Configuration option 'clr enabled' changed from 1 to 1. Run the RECONFIGURE statement to install.

Now run the RECONFIGURE command as requested by SQL:

RECONFIGURE 
GO

-- Command(s) completed successfully.

Load our .NET CLR UDF into the SQL Database

 Load the DLL file into the SQL server database (the DLL can be safely deleted from the file system once this step is complete):

CREATE ASSEMBLY e4Splitter
FROM 'C:\Users\Dave\Documents\visual studio 2010\Projects\MyStringSplitter\bin\Debug\MyStringSplitter.dll'
WITH PERMISSION_SET = SAFE
GO

-- Output: Command(s) completed successfully.

Note, if at this point if you get this error...
CREATE ASSEMBLY for assembly <Assembly Name> failed because the assembly is built for an unsupported version of the Common Language Runtime

...then you need to change the Target Framework to a different option in the Advanced Compiler Settings dialog (I used 3.5)

 

Create the SQL Function from our .Net CLR UDF

 CREATE FUNCTION dbo.myfn_StringSplitterCLR
    (
    @Ids nvarchar(max),
    @separator nchar(1) 
    )
RETURNS TABLE
    (
    IdVal int NULL
    )
AS EXTERNAL NAME [SplitterAssembly].[MyStringSplitter.MyStringSplitter].[SplitToIntegers]
GO
-- Output: Command(s) completed successfully.

 If you receive this error at this point:

Could not find Type '<TypeName>' in assembly 'MyStringSplitter'

 Check out this article: T-SQL error: Could not find type <TypeName> in assembly <AssemblyName>

Try out the new CLR UDF

Now let's test out the new function, again using 100,001 values:

SELECT * FROM myfn_StringSplitterCLR(',','8952, 5804, 9027, 5521, 5379, ..... removed for brevity...., 4939, 2900, 8507, 1211, 4366, 9743, 4612, 9035,')

 

Massive Performance Gain!

This time the query only took 1.926 seconds to complete, which (cue bad maths) is about 145 times faster!  Wow.  I hope you're convinced.

 

 

Comments

No comments have been posted. Be the first to write a comment...

Post a Comment

 

If this content saves you time or money, please consider returning a favour by...