Server Programming in .NET

30.10 Server Programming in .NET

SQL S erver supports the hosting of the . NET Common Language Runtime ( CLR ) inside the SQL S erver process to enable database programmers to write business logic as functions, stored procedures, triggers, data types, and aggregates. The ability to run application code inside the database adds flexibility to the design of application architectures that require business logic to execute close to the data and cannot afford the cost of shipping data to a middle-tier process to perform computation outside the database.

The . NET Common Language Runtime ( CLR ) is a runtime environment with

a strongly typed intermediate language that executes multiple modern program- ming languages such as C#, Visual Basic, C++, COBOL , and J++, among others, and has garbage-collected memory, preemptive threading, metadata services (type re- flection), code verifiability, and code access security. The runtime uses metadata to locate and load classes, lay out instances in memory, resolve method invocations, generate native code, enforce security, and set runtime context boundaries.

Application code is deployed inside the database by using assemblies , which are the units of packaging, deployment, and versioning of application code in . NET . Deployment of application code inside the database provides a uniform way to administer, back up, and restore complete database applications (code and data). Once an assembly is registered inside the database, users can expose entry points within the assembly via SQL DDL statements, which can act as scalar or table functions, procedures, triggers, types, and aggregates, by using well-defined extensibility contracts enforced during the execution of these DDL statements. Stored procedures, triggers, and functions usually need to execute SQL queries and updates. This is achieved through a component that implements the ADO.NET data-access API for use inside the database process.

1254 Chapter 30 Microsoft SQL Server 30.10.1 Basic .NET Concepts

In the . NET framework, a programmer writes program code in a high-level pro- gramming language that implements a class defining its structure (e.g., the fields or properties of the class) and methods. Some of these methods can be static functions. The compilation of the program produces a file, called an assembly, containing the compiled code in the Microsoft Intermediate Language ( MSIL ), and a manifest containing all references to dependent assemblies. The manifest is an integral part of every assembly that renders the assembly self-describing. The assembly manifest contains the assembly’s metadata, which describes all struc- tures, fields, properties, classes, inheritance relationships, functions, and methods defined in the program. The manifest establishes the assembly identity, specifies the files that make up the assembly implementation, specifies the types and re- sources that make up the assembly, itemizes the compile-time dependencies on other assemblies, and specifies the set of permissions required for the assembly to run properly. This information is used at runtime to resolve references, enforce version-binding policy, and validate the integrity of loaded assemblies. The . NET framework supports an out-of-band mechanism called custom attributes for anno- tating classes, properties, functions and methods with additional information or facets the application may want to capture in metadata. All . NET compilers con- sume these annotations without interpretation and store them in the assembly’s metadata. All these annotations can be examined in the same way as any other metadata by using a common set of reflection API s. Managed code refers to MSIL executed in the CLR rather than directly by the operating system. Managed-code applications gain common-language runtime services such as automatic garbage collection, runtime type checking, and security support. These services help pro- vide uniform platform- and language-independent behavior of managed-code applications. At execution time, a just-in-time ( JIT ) compiler translates the MSIL into native code (e.g., Intel X86 code). During this translation, code must pass a verification process that examines the MSIL and metadata to find out whether the code can be determined to be type safe.

30.10.2 SQL CLR Hosting

SQL S erver and the CLR are two different runtimes with different internal models for threading, scheduling and memory management. SQL S erver supports a coop- erative non-preemptive threading model in which the DBMS threads voluntarily yield execution periodically or when they are waiting on locks or I/O , whereas the CLR supports a preemptive threading model. If user code running inside the DBMS can directly call the operating-system ( OS ) threading primitives, then it does not integrate well with the SQL S erver task scheduler and can degrade the scalability of the system. CLR does not distinguish between virtual and physical memory, while SQL S erver directly manages physical memory and is required to use physical memory within a configurable limit.

The different models for threading, scheduling, and memory management present an integration challenge for a DBMS that scales to support thousands of concurrent user sessions. SQL S erver solves this challenge by becoming the

30.10 Server Programming in .NET 1255

SQL Server Engine

CLR

SQL Server

SQLCLR

Process

Hosting SQL OS Layer

(memory, threads, synchronization) Windows

Figure 30.5 Integration of CLR with SQL Server operating-system services.

operating system for the CLR when it is hosted inside the SQL S erver process. The CLR calls low-level primitives implemented by SQL S erver for threading, scheduling, synchronization, and memory management (see Figure 30.5). This approach provides the following scalability and reliability benefits:

Common threading, scheduling, and synchronization. CLR calls SQL S erver API s for creating threads both for running user code and for its own internal use such as the garbage collector and the class finalizer thread. In order to synchronize between multiple threads, the CLR calls SQL S erver synchronization objects. This allows SQL S erver scheduler to schedule other tasks when a thread is waiting on a synchronization object. For instance, when the CLR initiates garbage collection, all of its threads wait for garbage collection to finish. Since the CLR threads and the synchronization objects they are waiting on are known to the SQL S erver scheduler, it can schedule threads that are running other database tasks not involving the CLR . Further, this enables SQL S erver to detect deadlocks that involve locks taken by CLR synchronization objects and employ traditional techniques for deadlock removal. The SQL S erver scheduler has the ability to detect and stop threads that have not yielded for a significant amount of time. The ability to hook CLR threads to SQL S erver threads implies that the SQL S erver scheduler can identify runaway threads running in the CLR and manage their priority, so that they do not consume significant CPU resources, thereby affecting the throughput of the system. Such runaway threads are suspended and put back in the queue. Repeat offenders are not allowed timeslices that are unfair to other executing workers. If an offender took 50 times the allowed quantum, it is punished for 50 “rounds” before being allowed to run again because the scheduler cannot tell when a computation is long and runaway versus long and legitimate.

Common memory management. The CLR calls SQL S erver primitives for al- locating and deallocating its memory. Since the memory used by the CLR is accounted for in the total memory usage of the system, SQL S erver can stay within its configured memory limits and ensure the CLR and SQL S erver are not compet- ing with each other for memory. Also, SQL S erver can reject CLR memory requests

1256 Chapter 30 Microsoft SQL Server

when the system is constrained and ask CLR to reduce its memory use when other tasks need memory.

30.10.3 Extensibility Contracts

All user-managed code running within the SQL S erver process interacts with DBMS components as an extension. Current extensions include scalar functions, table functions, procedures, triggers, scalar types, and scalar aggregates. For each ex- tension there is a mutual contract defining the properties or services user code must implement to act as one of these extensions as well as the services the ex- tension can expect from the DBMS when the managed code is called. SQL CLR leverages the class and custom attributes information stored in assembly meta- data to enforce that user code implements these extensibility contracts. All user assemblies are stored inside the database. All relational and assembly metadata are processed inside the SQL engine through a uniform set of interfaces and data structures. When data-definition language ( DDL ) statements registering a partic- ular extension function, type, or aggregate are processed, the system ensures the user code implements the appropriate contract by analyzing its assembly meta- data. If the contract is implemented, then the DDL statement succeeds, otherwise it fails. The next subsections describe key aspects of the specific contracts currently enforced by SQL S erver.