sql server 2005: the clr integration what developers and dbas need to know
TRANSCRIPT
SQL Server 2005: The CLR IntegrationSQL Server 2005:
The CLR Integration
What Developers and DBAs need to know
What Developers and DBAs need to know
Who I AmWho I Am
Shawn Wildermuth ([email protected]) Senior Consultant/Architect with Magenic
Technologies (http://www.magenic.com) C# MVP INETA Speaker Book Author – “Pragmatic ADO.NET”;
Editor of http://ONDotnet.com This Presentation can be found at:– http://adoguy.com/presentations
Shawn Wildermuth ([email protected]) Senior Consultant/Architect with Magenic
Technologies (http://www.magenic.com) C# MVP INETA Speaker Book Author – “Pragmatic ADO.NET”;
Editor of http://ONDotnet.com This Presentation can be found at:– http://adoguy.com/presentations
AgendaAgenda
CLR Hosting Environment Assembly Management Managed Stored Procedures and
Functions InProc Managed Provider Database connections? Table Valued Functions Managed Triggers Managed Datatypes Custom Aggregates
CLR Hosting Environment Assembly Management Managed Stored Procedures and
Functions InProc Managed Provider Database connections? Table Valued Functions Managed Triggers Managed Datatypes Custom Aggregates
CLR Hosting EnvironmentCLR Hosting Environment
In .NET 1.1– Designed for IIS– Processes could be killed– Freely creating memory and threads
In .NET 2.0– Designed for SQL Server 2005– Processes cannot be killed– Host can refuse memory or thread
creation– Security critical
In .NET 1.1– Designed for IIS– Processes could be killed– Freely creating memory and threads
In .NET 2.0– Designed for SQL Server 2005– Processes cannot be killed– Host can refuse memory or thread
creation– Security critical
Assembly ManagementAssembly Management
DML Command to add an Assembly– Can be from a file
But has to have permissions (NT Security)
– Alternatively, can be a bitstream
– No In-Engine Compilation
DML Command to add an Assembly– Can be from a file
But has to have permissions (NT Security)
– Alternatively, can be a bitstream
– No In-Engine Compilation
CREATE ASSEMBLY ExampleYukonFROM 'd:\ExampleYukon.dll'
CREATE ASSEMBLY ExampleYukonFROM 0x4D5A90000300000004000000FFFF0000B8000000000000...
Assembly Management (2)Assembly Management (2)
Code Access Security (CAS) for Assemblies– Three CAS Buckets
SAFE– Access to the CLR only– No access to external resources, thread management,
unsafe code or interop
EXTERNAL_ACCESS– Access to external systems through the .NET Framework– E.g. EventLog, FileSystem and Network– No access unsafe or interop
UNSAFE– No restrictions; similar to extended stored procedures
Code Access Security (CAS) for Assemblies– Three CAS Buckets
SAFE– Access to the CLR only– No access to external resources, thread management,
unsafe code or interop
EXTERNAL_ACCESS– Access to external systems through the .NET Framework– E.g. EventLog, FileSystem and Network– No access unsafe or interop
UNSAFE– No restrictions; similar to extended stored procedures
Assembly Management (3)Assembly Management (3)
Use DML Assembly Commands for CAS
Use DML Assembly Commands for CASCREATE ASSEMBLY ExampleYukonFROM 'd:\ExampleYukon.dll'WITH PERMISSION_SET=SAFE
CREATE ASSEMBLY ExampleYukonFROM 'd:\ExampleYukon.dll'WITH PERMISSION_SET=EXTERNAL_ACCESS
CREATE ASSEMBLY ExampleYukonFROM 'd:\ExampleYukon.dll'WITH PERMISSION_SET=UNSAFE
Managed CodeManaged Code
Code isn’t available by default– Must register functions, stored procs, etc.– Code is not available by default– Registration takes certain permissions to
allow
Code isn’t available by default– Must register functions, stored procs, etc.– Code is not available by default– Registration takes certain permissions to
allow
Managed Code (2)Managed Code (2)
Attributes– Hints to VS about how to Deploy
[SqlProcedure] [SqlFunction] [SqlUserDefinedType] [SqlUserDefinedAggregate] Etc.
– Also used at runtime for behaviors of objects E.g. SqlMethod(IsMutable=true)
Attributes– Hints to VS about how to Deploy
[SqlProcedure] [SqlFunction] [SqlUserDefinedType] [SqlUserDefinedAggregate] Etc.
– Also used at runtime for behaviors of objects E.g. SqlMethod(IsMutable=true)
Managed Stored ProceduresManaged Stored Procedures
To expose a Stored Procedure:– The containing class must be public– The exposed method must be public– The exposed method must be static
To expose a Stored Procedure:– The containing class must be public– The exposed method must be public– The exposed method must be static
public class SqlClr { public static void MyProc() { // Put your code here }}
Managed Stored Procedures (2)Managed Stored Procedures (2)
Managed Stored Procedure DML– Uses the CREATE PROCEDURE call– Adds AS EXTERNAL NAME to specify CLR
SP
Managed Stored Procedure DML– Uses the CREATE PROCEDURE call– Adds AS EXTERNAL NAME to specify CLR
SPCREATE PROCEDURE <Procedure Name>AS EXTERNAL NAME <Assembly Identifier>.<Type Name>.<Method Name>
CREATE PROCEDURE MyProcAS EXTERNAL NAME ExampleYukon.SqlClr.MyProc
Managed Stored Procedures (3)Managed Stored Procedures (3)
Parameters – Must match DML Command:
Parameters – Must match DML Command:
// Input Parameterpublic static void InputProcedure(int number) {}
// Output Parameterpublic static void OutputProcedure(out int number) { number = 42;}
// In/Out Parameterpublic static void InOutProcedure(ref int number) { number = 42;}
// Return Parameterpublic static int ReturnProcedure() { return 42;}
Managed Stored Procedures (4)Managed Stored Procedures (4)
Parameters (continued)– DML must match the parameters:
Parameters (continued)– DML must match the parameters:
CREATE PROCEDURE InputProcedure @number intAS EXTERNAL NAME ExampleYukon.SqlClr.InputProcedure
CREATE PROCEDURE OutputProcedure @number int OUTPUTAS EXTERNAL NAME ExampleYukon.SqlClr.OutputProcedure
CREATE PROCEDURE InOutProcedure @number int OUTPUTAS EXTERNAL NAME ExampleYukon.SqlClr.InOutProcedure
-- NOTE: You don’t specify ReturnParametersCREATE PROCEDURE ReturnProcedureAS EXTERNAL NAME ExampleYukon.SqlClr.ReturnProcedure
Managed FunctionsManaged Functions
Similar to Stored Procedures– Uses similar DML Syntax:
Similar to Stored Procedures– Uses similar DML Syntax:
CREATE FUNCTION <Function Name>(<Parameter List>) RETURNS <Return Type>AS EXTERNAL NAME <Assembly Identifier>.<Type Name>.<Method Name>
CREATE FUNCTION Addition( @x int, @y int)RETURNS intAS EXTERNAL NAME ExampleYukon.SqlClr.Addition
Managed Functions (2)Managed Functions (2)
Code is similar to Stored Procedures
– [SqlFunction] Parameters Helps SQL Server know what the function
does– IsDeterministic– IsPrecise– DataAccess– SystemDataAccess
Code is similar to Stored Procedures
– [SqlFunction] Parameters Helps SQL Server know what the function
does– IsDeterministic– IsPrecise– DataAccess– SystemDataAccess
[SqlFunction]public static int Addition(int x, int y) { return x + y;}
Managed TriggersManaged Triggers
Also like Stored Procedures– DML Triggers
– Also, DDL Triggers
Also like Stored Procedures– DML Triggers
– Also, DDL Triggers
CREATE TRIGGER <TriggerName>ON <Table or View> <FOR|INSTEAD OF> <INSERT|UPDATE|DELETE>AS EXTERNAL NAME <Assembly Identifier>.<Type Name>.<Method Name>
CREATE TRIGGER AddContactON author FOR INSERTAS EXTERNAL NAME ExampleYukon.SqlClr.AddContact
CREATE TRIGGER <TriggerName>ON <ALL_SERVER or DATABASE> <FOR or AFTER> <EventName>AS EXTERNAL NAME <Assembly Identifier>.<Type Name>.<Method Name>
CREATE TRIGGER AddUserON DATABASE FOR CreateUserAS EXTERNAL NAME ExampleYukon.SqlClr.AddUser
Managed Triggers (2)Managed Triggers (2)
TriggerContext for Trigger Info– ColumnsUpdates to see what columns
changed
TriggerContext for Trigger Info– ColumnsUpdates to see what columns
changedpublic static void AddAuthor() { SqlTriggerContext ctx = SqlContext.TriggerContext;
if (ctx.TriggerAction == TriggerAction.Insert) { string msg = ""; // Get the data associated with Event for (int x = 0; x < ctx.ColumnCount; ++x) { msg += string.Format("Column {0} {1} been updated{2}", x, (ctx.IsColumnsUpdated(x) ? "has" : "has not"), Environment.NewLine) } // Use the message to log it somewhere }}
Custom AggregatesCustom Aggregates
Write your own MIN, MAX, COUNT– Define a CLR Type that does the
aggregation Use DML command to register
Write your own MIN, MAX, COUNT– Define a CLR Type that does the
aggregation Use DML command to registerCREATE AGGREGATE <Aggregate Name>
( @param <SQL Type> )RETURNS <SQL Type>EXTERNAL NAME <assembly>.<CLR Type>
CREATE AGGREGATE CustomAverage( @value float )RETURNS floatEXTERNAL NAME ExampleYukon.CustomAverage
Custom Aggregates (2)Custom Aggregates (2)
CLR Class– Class is called during the aggregation– Not just passed a set of values, but one at a time– Must be serializable (for intermediate results)– Must implement known methods
Init– Called at the start of the aggregation of fields
Accumulate– Called once for each row being aggregated. It is passed
the value from the column being aggregated. Merge
– Called to merge a temporary result into the aggregate Terminate
– Called at the end of the aggregation process to retrieve the result of the aggregation.
CLR Class– Class is called during the aggregation– Not just passed a set of values, but one at a time– Must be serializable (for intermediate results)– Must implement known methods
Init– Called at the start of the aggregation of fields
Accumulate– Called once for each row being aggregated. It is passed
the value from the column being aggregated. Merge
– Called to merge a temporary result into the aggregate Terminate
– Called at the end of the aggregation process to retrieve the result of the aggregation.
Custom Aggregates (3)Custom Aggregates (3)
Custom Average Example: Custom Average Example:[Serializable][SqlUserDefinedAggregate(Format.UserDefined, MaxByteSize = 8000)]public class CustomAverage : IBinarySerialize {
SqlDouble _total = 0; ulong _totalCount = 0;
public void Init() { } public void Accumulate(SqlDouble Value) { ++_totalCount; if (_total.IsNull) _total = 0; _total += Value; } public void Merge(StdDev grp) {/*...*/ } public SqlDouble Terminate() { return _total/_totalCount; }
void IBinarySerialize.Read(BinaryReader r) { /*...*/ } void IBinarySerialize.Write(BinaryWriter w) { /*...*/ }}
InProc Managed ProviderInProc Managed Provider
Inside the Server, a new Provider– Very similar to SqlClient Provider– Follows Managed Provider Model– Supports
IDbConnection IDbCommand IDbDataReader Etc.
Inside the Server, a new Provider– Very similar to SqlClient Provider– Follows Managed Provider Model– Supports
IDbConnection IDbCommand IDbDataReader Etc.
InProc Managed Provider (2)InProc Managed Provider (2)
Dude, where’s my Connection?– In Out-of-proc ADO.NET:
Dude, where’s my Connection?– In Out-of-proc ADO.NET:
using (SqlConnection conn = new SqlConnection("..."))using (SqlCommand cmd = conn.CreateCommand()) { try { cmd.CommandText = "..."; conn.Open();
using (SqlDataReader rdr = cmd.ExecuteReader()) { while (rdr.Read()) { Console.WriteLine(rdr.GetString(0)); } } // Reader is disposed } finally { conn.Close() }} // using statements ensure Disposed is called
InProc Managed Provider (3)InProc Managed Provider (3)
InProc you can assume the connection InProc you can assume the connectionusing (SqlCommand cmd = new SqlCommand()) { cmd.CommandText = "...";
SqlContext.Pipe.ExecuteAndSend(cmd);}
InProc Managed Provider (4)InProc Managed Provider (4)
If you need a connection– Can create additional connections– Use “context connection=true” for
current
If you need a connection– Can create additional connections– Use “context connection=true” for
currentusing (SqlConnection conn = new SqlConnection("context connection=true"))using (SqlCommand cmd = conn.CreateCommand()) { cmd.CommandText = @"SELECT * FROM Sales.SalesOrderHeader";
try { conn.Open();
SqlDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()) total += Convert.ToDecimal(rdr["TotalDue"]);
} finally { conn.Close(); }}
InProc Managed Provider (5)InProc Managed Provider (5)
SqlContext– Used to get active server objects
Pipe WindowsIdentity Etc.
SqlContext– Used to get active server objects
Pipe WindowsIdentity Etc.
InProc Managed Provider (6)InProc Managed Provider (6)
Returning Data– Use a Pipe to send data back to the client
Use the SqlContext’s Pipe Use Execute() to Fire a Command into the
pipe Use Send() to send results back from a
Reader
Returning Data– Use a Pipe to send data back to the client
Use the SqlContext’s Pipe Use Execute() to Fire a Command into the
pipe Use Send() to send results back from a
ReaderSqlCommand cmd = new SqlCommand();cmd.CommandText = "SELECT * FROM Customer";
// Send the results to the clientSqlPipe pip = SqlContext.Pipe.Execute(cmd);
SqlCommand cmd = new SqlCommand();cmd.CommandText = "SELECT * FROM Customer";SqlDataReader rdr = cmd.ExecuteReader();
// Send the results to the clientSqlContext.Pipe.Send(rdr);
InProc Managed Provider (7)InProc Managed Provider (7)
Returning Custom Results– Use SqlDataRecord
Must create SqlMetaData to describe the result
Returning Custom Results– Use SqlDataRecord
Must create SqlMetaData to describe the result
SqlMetaData[] columns = new SqlMetaData[3];columns[0] = new SqlMetaData("ID", SqlDbType.Int);columns[1] = new SqlMetaData("Name", SqlDbType.NVarChar, 50);columns[2] = new SqlMetaData("theDate", SqlDbType.DateTime);
SqlDataRecord record = new SqlDataRecord(columns);
record.SetInt32(0, 1);record.SetString(1, "Bob Higgins");record.SetDateTime(2, DateTime.Now);
SqlContext.Pipe.Send(record);
InProc Managed Provider (8)InProc Managed Provider (8)
Return Custom Results (Multiple Rows)– Use SendResultsStart to begin– Must send all rows with SendResultsRow– Must end with SendResultsEnd
Return Custom Results (Multiple Rows)– Use SendResultsStart to begin– Must send all rows with SendResultsRow– Must end with SendResultsEnd// Start it
SqlContext.Pipe.SendResultsStart(record); // Only primes pumpSqlContext.Pipe.SendResultsRow(record);
SqlDataRecord anotherRecord = new SqlDataRecord(columns);anotherRecord.SetInt32(0, 2);anotherRecord.SetString(1, "Mary Roberts");anotherRecord.SetDateTime(2, DateTime.Now.AddDays(1));SqlContext.Pipe.SendResultsRow(anotherRecord);
// Finish ResultSet SqlContext.Pipe.SendResultsEnd();
InProc Managed Provider (9)InProc Managed Provider (9)
SQL Server Datatypes– Use System.Data.SqlTypes namespace– Each SqlXXXXX type is INullable– Allows Stored Procs that allow DB Nulls
SQL Server Datatypes– Use System.Data.SqlTypes namespace– Each SqlXXXXX type is INullable– Allows Stored Procs that allow DB Nullspublic static void GetContact(SqlString email) { SqlCommand cmd = new SqlCommand(); cmd.CommandText = @"SELECT FirstName, LastName FROM Person.COntact WHERE EmailAddress = @email";
cmd.Parameters.Add("@email", SqlDbType.NVarChar, 50); cmd.Parameters["@email"].Value = email;
SqlContext.Pipe.ExecuteAndSend(cmd);}
InProc Managed Provider (7)InProc Managed Provider (7)
Updating Data– No Managed Cursor support in Whidbey– Must do Insert/Update/Delete as
necessary
Updating Data– No Managed Cursor support in Whidbey– Must do Insert/Update/Delete as
necessary
Managed User DatatypesManaged User Datatypes
Pre-SQL Server 2005– User Datatypes were aliases or restrictions
SQL Server 2005– Can store CLR Type as a Datatype– Rules are embedded in the CLR Type
Pre-SQL Server 2005– User Datatypes were aliases or restrictions
SQL Server 2005– Can store CLR Type as a Datatype– Rules are embedded in the CLR Type
EXEC sp_addtype N'age', N'tinyint', N'not null'GO
CREATE RULE age_rangeAS@age >= 0 AND @age <=140GO
EXEC sp_bindrule N'age_range', N'age'GO
Managed User Datatypes (2)Managed User Datatypes (2)
Requirements– Supports the concept of NULL (INullable)– Supports conversion to and from string– Supports serialization– Supports a default constructor– Type and member conform to naming
rules (128 character maximum)
Requirements– Supports the concept of NULL (INullable)– Supports conversion to and from string– Supports serialization– Supports a default constructor– Type and member conform to naming
rules (128 character maximum)
Managed User Datatypes (3)Managed User Datatypes (3)
Registering the UDT– Use CREATE TYPE DML Statement
Registering the UDT– Use CREATE TYPE DML Statement
CREATE TYPE <database type name>EXTERNAL NAME <assembly name>.<CLR type name>
CREATE TYPE PointEXTERNAL NAME ExampleYukon.Point
Managed User Datatypes (4)Managed User Datatypes (4)
Example: Example:[Serializable][SqlUserDefinedType(Format.UserDefined, MaxByteSize=8)]public class Point : INullable, IBinarySerialize { bool isNull = false; int x; int y;
// Required constructor public Point() : this(0, 0) { }
public Point(int x, int y) { this.x = x; this.y = y; } // ...}
Managed User Datatypes (5)Managed User Datatypes (5)
Using a UDT– Use CONVERT to switch from string to
type
Using a UDT– Use CONVERT to switch from string to
typeDECLARE @p as dbo.PointSET @p = Convert(dbo.Point, '3,8')SELECT @p
Managed User Datatypes (5)Managed User Datatypes (5)
Datatype methods– Marking it with SqlMethod can expose it
IsMutator shows SQL Server that it changes itself:
Can use with the point syntax:
Datatype methods– Marking it with SqlMethod can expose it
IsMutator shows SQL Server that it changes itself:
Can use with the point syntax:
[SqlMethod(IsMutator=true)]public void Swap() { int temp = x; x = y; y = temp;}
DECLARE @p as dbo.PointSET @p = Convert(dbo.Point, '3,8')SELECT @[email protected]()SELECT @p
Questions?Questions?