Developing Custom Managed Database Objects This chapter examines the deep integration of the .NET Framework with SQL Server 2008.. Developing Custom Managed Database Objects SQL Server 2
Trang 1This page intentionally left blank
Trang 2SQLCLR: Developing
SQL Server Objects in
.NET
What’s New for SQLCLR in SQL Server 2008
Developing Custom Managed Database Objects
This chapter examines the deep integration of the NET
Framework with SQL Server 2008 It delves into SQL Server
2008’s support for the creation of custom managed database
objects, otherwise known as SQLCLR.
What’s New for SQLCLR in SQL
Server 2008
Although there are only a few changes to SQLCLR in SQL
Server 2008, including support for multiparameter
aggre-gates, support for large-value data types (such as
nvarchar(MAX)) and support for the new SQL Server 2008
data types (such as dateandtime), SQLCLR itself
intro-duces a wealth of new possibilities for developing with NET
and SQL Server that you should consider leveraging in your
data-driven applications
In the following sections, we give you all the tools you
need to write your own stored procedures, functions,
trig-gers, data types, and aggregate functions in C# or VB NET
Developing Custom Managed
Database Objects
SQL Server 2008 hosts the common language runtime
(CLR), implementing what’s known as the Hosting API The
Hosting API gives SQL Server 2008 full control over the
execution of NET code in a carefully managed environment
that honors the shared resource usage of both SQL Server
and the CLR The CLR provides an execution context far
Trang 3CHAPTER 46 SQLCLR: Developing SQL Server Objects in NET
safer than that of code you might formerly have run in an extended stored procedure or
COM object under SQL Server 2000 and previous editions
In the sections that follow, you will create one of each of the managed versions of
data-base routines and types You work with both the SQL Server project type in Visual Studio
2008 as well as the T-SQL Data Definition Language (DDL) syntax for managed objects
Finally, you’ll learn about advanced topics such as transaction control in mixed (T-SQL
and managed) environments
An Introduction to Custom Managed Database Objects
The capability to run managed code presents a world of possibilities, yet these features
must be leveraged appropriately The meaning of appropriate will ultimately be the result
of ongoing dialogs between database administrators and the developers who want to use
the NET Framework in SQL Server
Just like SQL Server’s capability to host web services (covered in Chapter 48, “SQL Server
Web Services”), this feature set begins to blur the line between SQL Server as a database
server and SQL Server as a lightweight application server
.NET assemblies are built using Visual Studio or the command-line compilers and then
literally uploaded into the database and loaded into memory on the same physical server
as the SQL Server instance CLR objects may therefore consume valuable server and
network resources
This scenario presents a challenging new management paradigm that database
administra-tors, managers, and developers have to negotiate Administrators are just beginning to
consider strategies for what kinds of NET code they should allow to run and in which
contexts Following are a few general rules to consider regarding when managed objects
should and should not be used:
Data selection and modification should always be performed using T-SQL because
that’s what it’s optimized to do You should not create a T-SQL wrapper in your NET
code
You should use managed code when you need to overcome the procedural
limita-tions of T-SQL, such as avoiding the use of nested cursors that connect to multiple
databases and other awkward constructs (SQL was never developed to be a
proce-dural language, only a set-based query language.)
You should use managed code when you want to extend the per-row or per-set
effects of routines to leverage managed resources, such as XML parsers, web services,
and custom code libraries
The software development staff still must decide what to do, but we can be thankful that
SQL Server has some rules of its own for what kinds of operations can be called and
under which permission sets, as discussed in the following section
Trang 4FIGURE 46.1 Blessed assemblies in the Add References dialog in Visual Studio 2008
Managed Object Permissions
The first thing to know about managed object permissions is that SQL Server has only
blessed a certain group of assemblies usable under each of the three SQL Server permission
sets
Figure 46.1 shows the Add References dialog for a SQL Server project in Visual Studio
2008, listing these NET Framework assemblies They are the only assemblies (aside from
user-created assemblies) that can be referenced in SQL Server projects Note that this list
doesn’t change in Visual Studio, regardless of the permission set used Note also that SQL
Server and/or Visual Studio walks down the reference chain to see whether any
refer-enced assemblies reference anything that is not blessed Therefore, you shouldn’t bother
trying to get around this list; there isn’t even a Browse button on the dialog box as there
is with the other project types
The Three Permission Sets
SQL Server has three built-in NET Code Access Security (CAS) permission sets that define
which kinds of operations can be executed at runtime Using the CAS layer is a huge
improvement over running extended stored procedures under default login credentials
because it allows for fine-grained permission granting and revocation
These are the permission sets, in increasing order of freedom:
SAFE
EXTERNAL_ACCESS
UNSAFE
These keywords are used in the DDL syntax for assemblies
Trang 5CHAPTER 46 SQLCLR: Developing SQL Server Objects in NET
Assuming that you have built an assembly targeted for SQL Server use (which you do in
the next section), you can use the following syntax for loading that assembly into your
database of choice:
CREATE ASSEMBLY AssemblyName [AUTHORIZATION LoginName]
FROM StringPathToAssemblyDll | BinaryDataValue
[WITH PERMISSION_SET (SAFE | EXTERNAL_ACCESS | UNSAFE) ]
This syntax is reasonably self-explanatory: you tell SQL Server the name of the assembly
and the path (using a UNC if needed) to it If you’re loading an assembly from a
varbinarycolumn, you supply the actual data that makes up the compiled code of the
assembly instead of the path to it (Visual Studio does this)
NOTE
CREATE ASSEMBLYandALTER ASSEMBLYare commands used by Visual Studio’s Deploy
feature, which does the managed code DDL work for you
TheWITH PERMISSION SETclause is optional, and it defaults to SAFE Marking an assembly
with the SAFEpermission set indicates that no external resources (for example, the
Registry, web services, file I/O) are going to be accessed The DDL will fail if assemblies
such as System.IOare referenced, and anything causing a permission demand for
execut-ing similar operations will result in an exception beexecut-ing thrown at runtime Markexecut-ing an
assembly with the EXTERNAL_ACCESSpermission set tells SQL Server that it will be using
resources such as networking, files, and so forth Assemblies such as System.Web.Services
(but not System.Web) may be referenced with this set
Marking an assembly with theUNSAFEpermission set tells SQL Server that not only might
external resources be used, but unmanaged code may even be invoked from managed code
Some assemblies in the NET Framework go so far as to tell the processes that ultimately
host them (such as SQL Server or Internet Explorer) about their relative safety, using a
specific NET attribute: HostProtectionAttribute(HPA)
The enumeration flags of the HPA’s parameter indicate to the host what kinds of
opera-tions the classes decorated with it may attempt Because documentation of the HPA with
regards to SQL Server is scant, it’s unclear whether SQL Server ultimately relies on the HPA
to determine what may be loaded (It seems to do so at runtime, but the blessed list is
likely to be hard coded.)
Following are some of the operations you cannot perform with NET code running under
SQL Server’s SAFEandEXTERNAL_ACCESSoptions (but possibly under UNSAFE):
Thread synchronization
External process management
Framework security changes
Use of non-read-only static fields
Trang 6Only those in the sysadminrole can upload UNSAFEassemblies to SQL Server (Just don’t
tell your DBA we told you how to do it.)
TheEXTERNAL_ACCESSpermission on masteris required for uploading EXTERNAL_ACCESS
assemblies And anyone in the db_ownerrole may load SAFEassemblies
Developing Managed Objects with Visual Studio 2008
When SQL Server 2008 is installed, it includesMicrosoft.SqlServer.Server, the
assem-bly that contains the attributes and other classes needed for SQLCLR (the common
acronym for managed code running in SQL Server) programming
The first step in working with SQLCLR is to enable the feature in SQL Server
Setting Up the Server for Managed Code Execution
Before you can work with managed database objects, you need to execute the following
T-SQL commands in the context of the masterdatabase:
sp_configure ‘clr enabled’, 1
RECONFIGURE
go
This step is necessary because SQL Server comes with managed code execution turned off
by default
At this point, you’re ready to create your first SQLCLR project using Visual Studio 2008
(VS) Start VS and create a new C#-based SQL Server project with a name of your choosing
(the example is named SQL2008SQLCLR) Figure 46.2 shows the Add New Project dialog
FIGURE 46.2 Using the Add New Project dialog in Visual Studio 2008 to create a SQLCLR
project
Trang 7CHAPTER 46 SQLCLR: Developing SQL Server Objects in NET
Next, VS asks you to create or add a reference to a database to which you will deploy your
new assembly During deployment, your assembly (and with it, all its SQLCLR types and
routines) is uploaded directly into SQL Server’s system tables Select AdventureWorks2008
so you can work with the examples that follow
Next, you create your first managed SQL Server object, a stored procedure written in C#
Developing Managed Stored Procedures
Stored procedures are a great starting point for getting into SQLCLR because they are easy
to implement To do so, right-click your new project in VS’s Solution Explorer and then
select Add, Stored Procedure Name your new class StoredProcedures.cs A partial class of
that name opens in the VS code window Note that VS automatically adds the required
reference to Microsoft.SqlServer.Serverand its associated usingstatement
Microsoft.SqlServer.Servercontains the SqlProcedureattribute required for turning
ordinary methods into SQLCLR stored procedures
Change your autogenerated method name from StoredProcedurestoGetProductReviews
Next, if you’re not working in VS, you need to decorate this method with the
SqlProcedureattribute
Attributes and the Implementation Contract
If you’ve never used attributes, you can think of them as metadata that tells callers,
usually through reflection, that the decorated element (known as the target) meets some
criterion All the managed objects you create in this chapter require certain attributes to
be applied; otherwise, they cannot be used in a SQL Server context
The classes you build must also implement particular methods and/or method signatures
to be built and deployed successfully to SQL Server This is known as fulfilling the
imple-mentation contract For stored procedures, fulfilling this contract requires that your method
be marked static Its return type must be one of the following: void,Int32,
Nullable<Int32>,orSqlInt32 Its input parameters and their types are up to you, but
keep in mind that these must be convertible from a T-SQL context to a NET context
These are the only contract requirements to be filled for stored procedures
NOTE
It makes sense that these methods must be marked staticbecause they are called
by the CLR host via the class’s type object, rather than via an object instance (that is,
AssemblyName.ClassName.StaticMethodName(Parameters) ).
Object-oriented (OO) purists might suggest that this way of creating managed SQL Server
objects could have been done in a more OO-friendly way if the contract to be filled
required overriding the methods of an abstract class or implementing interfaces The static
requirement, however, currently makes this impossible because static members are not
inherited and cannot be used to implement interface members
Trang 8The constructor for the SqlProcedureattribute is overloaded to either take zero parameters
or take one parameter that is actually a list of named parameters (Having a list of named
parameters in the attribute signature is common to most of the attributes used in this
chapter, although the choice of named parameter pairs varies from attribute to attribute.)
For theSqlStoredProcedureAttribute, only one named parameter exists:Name You use
Namewhen you want to name the method one thing but have the name it generates for
use in a T-SQL context to be another name
The code in Listing 46.1 illustrates the use of a named parameter in this attribute and
contains a simple example of how to generate a set of rows using SQLCLR
LISTING 46.1 A Managed Stored Procedure That Generates a Set of Rows
[SqlProcedure(Name = “clr_GetProductManuals”)]
public static void GetProductManuals()
{
using (SqlConnection ContextConnection =
new SqlConnection(“context connection=true”))
{
SqlDataRecord record =
new SqlDataRecord(
new SqlMetaData[]
{
new SqlMetaData(“ProductModelId”, SqlDbType.Int),
new SqlMetaData(“Manual”, SqlDbType.Xml)
}
);
SqlContext.Pipe.SendResultsStart(record);
using (SqlCommand Command = new SqlCommand())
{
Command.CommandText =
@”SELECT TOP 10 ProductModelId, Instructions FROM Production.ProductModel
WHERE Instructions IS NOT NULL”;
Command.Connection = ContextConnection;
ContextConnection.Open();
using (SqlDataReader reader = Command.ExecuteReader())
{
while (reader.Read()) {
Trang 9CHAPTER 46 SQLCLR: Developing SQL Server Objects in NET
int ProductModelId = reader.GetInt32(0);
SqlXml ManualXml = reader.GetSqlXml(1);
record.SetInt32(0, ProductModelId);
record.SetSqlXml(1, ManualXml);
SqlContext.Pipe.SendResultsRow(record);
} }
SqlContext.Pipe.SendResultsEnd();
}
}
}
Although this example does not require it, if your SQLCLR code needs to access server
resources (such as files), it would be necessary to change your assembly’s permission set
from the default ofSAFEtoEXTERNAL_ACCESS To do to this in VS, right-click your project
in Solution Explorer and select Properties Then, on the left side of the window, select the
Database tab (Note that the Database tab is the place where VS stores a connection string
matching your database reference You can change that here as well.) Under the Permission
Level drop-down, change the value from Safe to External and save the project You can also
type in the name of the SQL Server login (under Assembly Owner), which will be specified
for theAUTHORIZATIONparameter ofCREATE ASSEMBLYduring deployment by VS
The idea behind the code in Listing 46.1 is that, given a set of rows from
Production.ProductModel, the procedure generates a result set of only those products that
have a reference manual Let’s examine the new objects in this code
The Context Connection
In our managed procedure, we use a special ADO.NET connection string (”context
connection=true”), known as the context connection string, to connect to the session of
SQL Server under which our managed stored procedure is currently executing (that is,
once the assembly has been deployed and is running in a T-SQL context)
Objects in Microsoft.SqlServer.Server
Our managed procedure also uses some specialized objects to send data to SQL Server
through the active connection:
SqlContext—This represents the server execution context for the managed routine
You can think of it as the line of communication between the NET and SQL Server
environments
SqlContext.Pipe—SqlContextholds the crucial Pipeproperty, used to send
SqlDataRecordobjects or text messages to the method’s caller, which, by the way,
may be either another managed routine (via ADO.NET) or any T-SQL user code
Trang 10SqlDataRecord—This is an abstraction that represents a record in any given table
The schema of columns for a SqlDataRecordobject is created by using SqlMetaData
objects, as shown in Listing 46.1
SqlMetaData—An array of SqlMetaDataobjects is passed to the constructor of each
SqlDataRecord Each SqlMetaDataobject defines the name, type, precision, scale,
and so forth for its target column via its overloaded constructors
Returning to the code in Listing 46.1, before looping through ourSqlDataReaderobject
(reader), we callPipe.SendResultsStart, passing aSqlDataRecordobject whose
struc-ture matches our desired output This tells SQL Server that our procedure is about to
send rows (to the caller) having a specific structure
Looping through the reader (using while (reader.Read())), we select the values to be
returned To do this, we use the Set[DataTypeName]methods on our SqlDataRecordobject
calledrecord When our values are all set, we call
SqlContext.Pipe.SendResultsRow(record)to return these data
After the code has finished sending data to the client, it cleans up by calling
Pipe.SendResultsEnd Note that the Pipeobject also has an ExecuteAndSendmethod that
takes a SqlCommandparameter, executes it, and sends all the results back to the caller in
one fell swoop In addition, you can query the status of the Pipeobject by checking its
IsSendingResultsBoolean property You can even send an informational text message
(similar to T-SQL’s printfunction) to the caller, using Pipe.Send(”Text”).Send()is
over-loaded to accept a SqlDataRecordobject or a SqlDataReaderobject that contains the data
to be returned
Building and Deploying the Assembly
At this point, you can build the VS project and then choose the new Deploy command
from VS’s Build menu In this step, VS generates the T-SQL DDL scripts needed to upload
our assembly to SQL Server and then add our managed stored procedure to the
AdventureWorks2008database
You’ve already seen the CREATE ASSEMBLYDDL that VS uses For now, let’s assume that
you’ve already uploaded the assembly once In this scenario, you need to execute the
following T-SQL to replace that assembly with a newly compiled version of the same:
ALTER ASSEMBLY AssemblyName
[AUTHORIZATION LoginName]
FROM StringPathToAssemblyDll | BinaryDataValue
[PERMISSION_SET = (SAFE | EXTERNAL_ACCESS | UNSAFE) ]
You can also use ALTER ASSEMBLYto upload your C# class files so that when you’re
debug-ging any exceptions you get source code line numbers in the stack dump (seamlessly
reported by SQL Server’s built-in error reporting mechanism) Here’s an example:
ALTER ASSEMBLY AssemblyName ADD FILE FROM FilePath