SQL Server 2005: The CLR Integration

Download Report

Transcript SQL Server 2005: The CLR Integration

SQL Server 2005:
The CLR Integration
What Developers and DBAs need
to know
Who 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
Agenda
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
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 Management
DML Command to add an Assembly
– Can be from a file
 But has to have permissions (NT Security)
CREATE ASSEMBLY ExampleYukon
FROM 'd:\ExampleYukon.dll'
– Alternatively, can be a bitstream
CREATE ASSEMBLY ExampleYukon
FROM 0x4D5A90000300000004000000FFFF0000B8000000000000...
– No In-Engine Compilation
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
Assembly Management (3)
Use DML Assembly Commands for CAS
CREATE ASSEMBLY ExampleYukon
FROM 'd:\ExampleYukon.dll'
WITH PERMISSION_SET=SAFE
CREATE ASSEMBLY ExampleYukon
FROM 'd:\ExampleYukon.dll'
WITH PERMISSION_SET=EXTERNAL_ACCESS
CREATE ASSEMBLY ExampleYukon
FROM 'd:\ExampleYukon.dll'
WITH PERMISSION_SET=UNSAFE
Managed 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
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)
Managed 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
public class SqlClr {
public static void MyProc() {
// Put your code here
}
}
Managed Stored Procedures (2)
Managed Stored Procedure DML
– Uses the CREATE PROCEDURE call
– Adds AS EXTERNAL NAME to specify CLR SP
CREATE PROCEDURE <Procedure Name>
AS EXTERNAL NAME <Assembly Identifier>.<Type Name>.<Method Name>
CREATE PROCEDURE MyProc
AS EXTERNAL NAME ExampleYukon.SqlClr.MyProc
Managed Stored Procedures (3)
Parameters
– Must match DML Command:
// Input Parameter
public static void InputProcedure(int number) {
}
// Output Parameter
public static void OutputProcedure(out int number) {
number = 42;
}
// In/Out Parameter
public static void InOutProcedure(ref int number) {
number = 42;
}
// Return Parameter
public static int ReturnProcedure() {
return 42;
}
Managed Stored Procedures (4)
Parameters (continued)
– DML must match the parameters:
CREATE PROCEDURE InputProcedure
@number int
AS EXTERNAL NAME ExampleYukon.SqlClr.InputProcedure
CREATE PROCEDURE OutputProcedure
@number int OUTPUT
AS EXTERNAL NAME ExampleYukon.SqlClr.OutputProcedure
CREATE PROCEDURE InOutProcedure
@number int OUTPUT
AS EXTERNAL NAME ExampleYukon.SqlClr.InOutProcedure
-- NOTE: You don’t specify ReturnParameters
CREATE PROCEDURE ReturnProcedure
AS EXTERNAL NAME ExampleYukon.SqlClr.ReturnProcedure
Managed Functions
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 int
AS EXTERNAL NAME ExampleYukon.SqlClr.Addition
Managed Functions (2)
Code is similar to Stored Procedures
[SqlFunction]
public static int Addition(int x, int y) {
return x + y;
}
– [SqlFunction] Parameters
 Helps SQL Server know what the function does
–
–
–
–
IsDeterministic
IsPrecise
DataAccess
SystemDataAccess
Managed Triggers
Also like Stored Procedures
– DML 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 AddContact
ON author FOR INSERT
AS EXTERNAL NAME ExampleYukon.SqlClr.AddContact
– Also, DDL Triggers
CREATE TRIGGER <TriggerName>
ON <ALL_SERVER or DATABASE> <FOR or AFTER> <EventName>
AS EXTERNAL NAME <Assembly Identifier>.<Type Name>.<Method Name>
CREATE TRIGGER AddUser
ON DATABASE FOR CreateUser
AS EXTERNAL NAME ExampleYukon.SqlClr.AddUser
Managed Triggers (2)
TriggerContext for Trigger Info
– ColumnsUpdates to see what columns changed
public 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 Aggregates
Write your own MIN, MAX, COUNT
– Define a CLR Type that does the aggregation
 Use DML command to register
CREATE AGGREGATE <Aggregate Name>
( @param <SQL Type> )
RETURNS <SQL Type>
EXTERNAL NAME <assembly>.<CLR Type>
CREATE AGGREGATE CustomAverage
( @value float )
RETURNS float
EXTERNAL NAME ExampleYukon.CustomAverage
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.
Custom Aggregates (3)
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 Provider
Inside the Server, a new Provider
– Very similar to SqlClient Provider
– Follows Managed Provider Model
– Supports
 IDbConnection
 IDbCommand
 IDbDataReader
 Etc.
InProc Managed Provider (2)
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 you can assume the connection
using (SqlCommand cmd = new SqlCommand()) {
cmd.CommandText = "...";
SqlContext.Pipe.ExecuteAndSend(cmd);
}
InProc Managed Provider (4)
If you need a connection
– Can create additional connections
– Use “context connection=true” for current
using (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)
SqlContext
– Used to get active server objects
 Pipe
 WindowsIdentity
 Etc.
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
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "SELECT * FROM Customer";
// Send the results to the client
SqlPipe pip = SqlContext.Pipe.Execute(cmd);
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "SELECT * FROM Customer";
SqlDataReader rdr = cmd.ExecuteReader();
// Send the results to the client
SqlContext.Pipe.Send(rdr);
InProc Managed Provider (7)
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)
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 pump
SqlContext.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)
SQL Server Datatypes
– Use System.Data.SqlTypes namespace
– Each SqlXXXXX type is INullable
– Allows Stored Procs that allow DB Nulls
public 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)
Updating Data
– No Managed Cursor support in Whidbey
– Must do Insert/Update/Delete as necessary
Managed User Datatypes
Pre-SQL Server 2005
– User Datatypes were aliases or restrictions
EXEC sp_addtype N'age', N'tinyint', N'not null'
GO
CREATE RULE age_range
AS
@age >= 0 AND @age <=140
GO
EXEC sp_bindrule N'age_range', N'age'
GO
SQL Server 2005
– Can store CLR Type as a Datatype
– Rules are embedded in the CLR Type
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)
Managed User Datatypes (3)
Registering the UDT
– Use CREATE TYPE DML Statement
CREATE TYPE <database type name>
EXTERNAL NAME <assembly name>.<CLR type name>
CREATE TYPE Point
EXTERNAL NAME ExampleYukon.Point
Managed User Datatypes (4)
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)
Using a UDT
– Use CONVERT to switch from string to type
DECLARE @p as dbo.Point
SET @p = Convert(dbo.Point, '3,8')
SELECT @p
Managed User Datatypes (5)
Datatype methods
– Marking it with SqlMethod can expose it
 IsMutator shows SQL Server that it changes itself:
[SqlMethod(IsMutator=true)]
public void Swap() {
int temp = x;
x = y;
y = temp;
}
 Can use with the point syntax:
DECLARE @p as dbo.Point
SET @p = Convert(dbo.Point, '3,8')
SELECT @p
@p.Swap()
SELECT @p
Questions?