March 14, 2010 by Christoff Truter C# SQL
Back in 2004 - 2005 I created extended stored procedures for SQL 2000 using C++, soon afterwards
we migrated to SQL 2005, which allows us to create stored procedures/functions/triggers/types etc
via the .net CLR (using C#) - providing similar functionality.
Its quite powerful functionality, that allows us to do things we can't normally do within SQL e.g.
my first CLR Procedure we used to send messages to mobile phones - which functioned as a notification
about certain events happening within a customer account.
Thinking a bit further - we can add some proper regex functionality to SQL, perhaps improve XQuery support to SQL XML types - I've seen some code where devs added functionality to query the Sharepoint webservice from CLR procedures.
The API which aid us with CLR integration is located within the
Microsoft.SqlServer.Server namespace
Lets have a quick look at creating some functions:
Trim function:
If you're one of those guys bugging microsoft into creating a trim function (and not into the whole ltrim+rtrim thing), why
not write your own trim function? (Which provides me the opportunity to give an example of a simple scalar function)
[SqlFunction(DataAccess = DataAccessKind.None)] public static String Trim([SqlFacet(MaxSize = -1)]String value) { return value.Trim(); }
CREATE ASSEMBLY CLRDemos FROM 'C:\procs\CLRDemo.dll' WITH PERMISSION_SET = SAFE;
sp_configure'clr enabled', 1 RECONFIGURE
CREATE FUNCTION TRIM ( @value NVARCHAR(MAX) ) RETURNS NVARCHAR (MAX) AS EXTERNAL NAME [CLRDemos].[CSTruter.com.functions].[Trim]
[SqlFunction(DataAccess = DataAccessKind.None, FillRowMethodName = "FillRow")] public static IEnumerable Split([SqlFacet(MaxSize = 10)]String separator, [SqlFacet(MaxSize = -1)]String value) { Int32 i = 0; String[] values = value.Split(new String[] { separator }, StringSplitOptions.None); foreach (String v in values) { yield return new Object[] { i++, v }; } } public static void FillRow(Object sender, out Int32 id, out String value) { Object[] values = (Object[])sender; id = (Int32)values[0]; value = (String)values[1]; }
CREATE FUNCTION SPLIT ( @separator NVARCHAR(10), @value NVARCHAR(MAX) ) RETURNS TABLE(id INT, value NVARCHAR(MAX)) AS EXTERNAL NAME [CLRDemos].[CSTruter.com.functions].[Split]
[SqlProcedure] public static void viewFriends() { using (SqlConnection connection = new SqlConnection("context connection=true")) { connection.Open(); SqlCommand command = new SqlCommand("SELECT firstname, lastname FROM friends", connection); SqlDataReader reader = command.ExecuteReader(); SqlContext.Pipe.Send(reader); } }
CREATE PROCEDURE viewFriends AS EXTERNAL NAME [CLRDemos].[CSTruter.com.Procedures].[viewFriends]
[SqlProcedure] public static void getDayNames([SqlFacet(MaxSize = 10)]String name) { SqlDataRecord record = new SqlDataRecord(new SqlMetaData[] { new SqlMetaData("id", System.Data.SqlDbType.Int), new SqlMetaData("value", System.Data.SqlDbType.VarChar, 255) }); CultureInfo culture = new CultureInfo(name); String[] DayNames = culture.DateTimeFormat.DayNames; SqlContext.Pipe.SendResultsStart(record); for (int i = 0; i < DayNames.Length; i++) { record.SetInt32(0, i); record.SetSqlString(1, DayNames[i]); SqlContext.Pipe.SendResultsRow(record); } SqlContext.Pipe.SendResultsEnd(); }
CREATE PROCEDURE getDayNames(@name AS NVARCHAR(10)) AS EXTERNAL NAME [CLRDemos].[CSTruter.com.Procedures].[getDayNames]