ptg This page intentionally left blank ptg CHAPTER 46 SQLCLR: Developing SQL Server Objects in .NET IN THIS CHAPTER . 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 date and time), 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 ptg 1826 CHAPTER 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. ptg 1827 Developing Custom Managed Database Objects FIGURE 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. ptg 1828 CHAPTER 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 varbinary column, 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 ASSEMBLY and ALTER ASSEMBLY are commands used by Visual Studio’s Deploy feature, which does the managed code DDL work for you. The WITH PERMISSION SET clause is optional, and it defaults to SAFE. Marking an assembly with the SAFE permission 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.IO are referenced, and anything causing a permission demand for execut- ing similar operations will result in an exception being thrown at runtime. Marking an assembly with the EXTERNAL_ACCESS permission 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 the UNSAFE permission 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 SAFE and EXTERNAL_ACCESS options (but possibly under UNSAFE): . Thread synchronization . External process management . Framework security changes . Use of non-read-only static fields ptg 1829 Developing Custom Managed Database Objects Only those in the sysadmin role can upload UNSAFE assemblies to SQL Server. (Just don’t tell your DBA we told you how to do it.) The EXTERNAL_ACCESS permission on master is required for uploading EXTERNAL_ACCESS assemblies. And anyone in the db_owner role may load SAFE assemblies. Developing Managed Objects with Visual Studio 2008 When SQL Server 2008 is installed, it includes Microsoft.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 master database: 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. ptg 1830 CHAPTER 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.Server and its associated using statement. Microsoft.SqlServer.Server contains the SqlProcedure attribute required for turning ordinary methods into SQLCLR stored procedures. Change your autogenerated method name from StoredProcedures to GetProductReviews. Next, if you’re not working in VS, you need to decorate this method with the SqlProcedure attribute. 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>, or SqlInt32. 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 static because 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. ptg 1831 Developing Custom Managed Database Objects The constructor for the SqlProcedure attribute 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 the SqlStoredProcedureAttribute, only one named parameter exists: Name. You use Name when 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()) { ptg 1832 CHAPTER 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 of SAFE to EXTERNAL_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 the AUTHORIZATION parameter of CREATE ASSEMBLY during 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—SqlContext holds the crucial Pipe property, used to send SqlDataRecord objects 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. ptg 1833 Developing Custom Managed Database Objects . SqlDataRecord—This is an abstraction that represents a record in any given table. The schema of columns for a SqlDataRecord object is created by using SqlMetaData objects, as shown in Listing 46.1. . SqlMetaData—An array of SqlMetaData objects is passed to the constructor of each SqlDataRecord. Each SqlMetaData object 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 our SqlDataReader object ( reader), we call Pipe.SendResultsStart, passing a SqlDataRecord object 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 SqlDataRecord object called record. 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 Pipe object also has an ExecuteAndSend method that takes a SqlCommand parameter, executes it, and sends all the results back to the caller in one fell swoop. In addition, you can query the status of the Pipe object by checking its IsSendingResults Boolean property. You can even send an informational text message (similar to T-SQL’s print function) to the caller, using Pipe.Send(”Text”). Send() is over- loaded to accept a SqlDataRecord object or a SqlDataReader object 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 AdventureWorks2008 database. You’ve already seen the CREATE ASSEMBLY DDL 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 ASSEMBLY to 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 . .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. required reference to Microsoft. SqlServer .Server and its associated using statement. Microsoft. SqlServer .Server contains the SqlProcedure attribute required for turning ordinary methods into SQLCLR stored. with Visual Studio 2008 When SQL Server 2008 is installed, it includes Microsoft. SqlServer .Server, the assem- bly that contains the attributes and other classes needed for SQLCLR (the common acronym