So far in this series we have looked at what SQLCLR is and what it can do. We have also looked at several examples related to the basic mechanics of SQLCLR and the various aspects of security. This time we finish learning the foundational aspects of doing SQLCLR development by exploring how .NET development within the context of SQL Server differs from regular .NET development.
The .NET Environment
Before we start looking at Visual Studio and getting into any code, we need to go over the nuances involved when working in SQL Server’s CLR. Even if you are very familiar with .NET you still need to be aware of environmental differences as there are some limitations that need to be understood. We need to look at features, supported .NET Framework libraries, and what version of .NET is in use, as there were significant changes introduced in SQL Server 2008 and then again in 2012. Between versions 2012 and 2014 nothing has changed so all examples here (and throughout this Stairway series) will work the same between those two versions. If any functionality is used that is not available in either just 2005, or in 2005, 2008, and 2008 R2, it will be noted.
Below is a chart of the changes to SQL Server’s CLR Integration over the ages:
SQL Server —» Category (below) | 2005 | 2008 / 2008 R2 | 2012 / 2014 |
---|---|---|---|
Supported .NET Framework Libraries |
|
| |
UDT: Max size (bytes) | 8000 | 2147483647 (i.e. 2 GB) | |
UDA: Max size (bytes) | 8000 | 2147483647 (i.e. 2 GB) | |
UDA: Input Parameters | 1 | 1 or more | |
TVF: ORDER clause | No | Yes | |
Memory Location and Amount Available (32-bit systems) | MTL (Mem-to-Leave) 256 MB by default | Max Server Memory | |
CLR version | 2.0 | 4.0 | |
.NET Framework versions | 2.0 / 3.0 / 3.5 | 4.0 / 4.5 / 4.5.x | |
.NET Framework version installed with SQL Server ¹ | 2.0 | 3.5 SP1 | 4.0 |
Performance Enhancements | n/a | {none} |
|
¹ Please note that any Microsoft documentation stating that a particular .NET Framework version is "required", whether or not it is installed with that version of SQL Server, is referring to the requirements of tools that come with SQL Server and is not related to the "CLR Integration" feature.
CLR Version and .NET Framework Versions
While this is not always a practical difference, it should at least be understood that the Common Language Runtime (CLR) that executes the .NET code is not the same thing as the .NET Framework. The reason it is important to note this difference is that a particular version of the CLR can work across several versions of the .NET Framework. SQL Server, starting with version 2005, is bound to a particular version of the CLR, not to a particular version of the .NET Framework. For example, SQL Server 2008 is bound to CLR version 2.0 and yet includes internally the System.Core library that was added in version 3.5 of the .NET Framework.
While .NET applications running on the main Windows CLR can make use of multiple versions of the CLR, the SQL Server-specific CLR host does not allow for mixed-mode CLR. Meaning, whatever version of the CLR is listed for a particular version of SQL Server, that is the only version that loaded assemblies will run against. Creating an assembly that uses a feature that started in .NET Framework 4.5 will error when trying to load that assembly in SQL Server 2005, 2008, or 2008 R2 due to those versions only supporting version 2.0 of the CLR while .NET Framework 4.5 requires version 4.0 of the CLR. However, due to functionality being backwards compatible, any assembly that works in SQL Server 2005, 2008, or 2008 R2 (assuming that it does not rely upon any unsupported .NET Framework libraries) should work just the same in SQL Server 2012 or 2014 (or whatever the current version is).
To be clear about the CLR versions and .NET Framework versions, the out-of-the-box functionality that you can expect to use for a particular version of SQL Server depends on the version of the CLR that the target version of SQL Server is bound to, and to the list of supported .NET Framework libraries. For SQL Server 2005, 2008, and 2008 R2—all bound to version 2.0 of the CLR—it is valid to use features that were introduced in .NET Framework versions 2.0, 3.0 and 3.5. Likewise, for SQL Server 2012 and 2014—both bound to version 4.0 of the CLR—it is valid to use functionality that was introduced in .NET Framework versions 4.0, 4.5, 4.5.1, and so on. There are two requirements for using functionality that was introduced in a .NET Framework version that is different than its associated version of the CLR (i.e. 3.0 and 3.5 running on version 2.0 of the CLR, and 4.5, 4.5.1, etc running on version 4.0 of the CLR):
- The functionality needs to be provided in one of the supported .NET Framework libraries (more on those in a moment). This means that you can reference the TimeZoneInfo class (introduced in .NET 3.5) if the assembly will be used in SQL Server 2008 or 2008 R2 because that class is found in System.Core. But it will not work, by default, if the assembly will be loaded into SQL Server 2005 as System.Core was not introduced until SQL Server 2008 (as shown in the chart above). If you try to load an assembly referencing TimeZoneInfo, and hence System.Core, into SQL Server 2005, you will get the following error when executing the CREATE ASSEMBLY statement:
Msg 6503, Level 16, State 12, Line 1
Assembly 'system.core, version=3.5.0.0, culture=neutral, publickeytoken=b77a5c561934e089.' was not found in the SQL catalog.
- The referenced versions of the .NET Framework need to be installed on any server that has an instance of SQL Server in which your assembly will be loaded. This is only an issue if the installed version of SQL Server did not come with the .NET Framework version that is being referenced. For example, SQL Server 2005 could use a feature that was introduced in .NET 3.0 and is found in one of the SQL Server 2005 supported .NET Framework libraries, but SQL Server 2005 only installs .NET Framework 2.0. If .NET 3.0 or even .NET 3.5 has not been manually installed on that server, then the assembly will not work (most likely the error will occur when executing
CREATE ASSEMBLY
). This is not an issue for SQL Server 2008 or 2008 R2 as both of those versions automatically install .NET 3.5 SP1. However, this does become an issue again in SQL Server 2012 and 2014 is using features that were introduced in .NET 4.5 or newer as those two versions of SQL Server only install .NET 4.0; using newer features requires manually installing the referenced version of the .NET Framework.
Supported .NET Framework Libraries
It was mentioned in the prior two levels that SQLCLR programming is restricted by Host Protection Attributes (HPAs) and other limitations. Those restrictions are configurable and can be lifted by changing the PERMISSION_SET
property of an assembly. But there is a whole other level of restrictions that are not configurable. As opposed to typical .NET programming (i.e. for Desktop and WebApps), SQL Server’s CLR host contains only a subset of the available .NET Framework libraries (and a fairly small subset at that). The libraries in the supported list are guaranteed to work as expected, across upgrades to .NET and/or SQL Server on your system.
The list of supported libraries is shown in the chart above. Starting in SQL Server 2008, two libraries—System.Core and System.Xml.Linq—were added and the list has been stable ever since (at least through SQL Server 2014).
Unsupported .NET Framework Libraries
It is possible to reference some .NET Framework libraries that are not in the supported list. However, this requires manually loading those libraries into SQL Server. In most, if not all, cases the assembly will need to be created with the option WITH PERMISSION_SET = UNSAFE
due to the library either not being verifiable itself, or depending on one or more libraries that are not verifiable (dependent libraries in the same folder are automatically imported when using CREATE ASSEMBLY
with a path to the DLL). And loading them as UNSAFE
will require setting the database as TRUSTWORTHY ON
.
While incorporating one or more unsupported Framework libraries can prove to be successful for years without any problems, you should at least be aware of the risks prior to making that choice:
- They are unsupported for a reason:
Unsupported Framework DLLs have not been fully tested and verified. There can be internationalization issues. There can be unexpected behavior due to whatever reason(s) required that the DLL to be loaded as
UNSAFE
. For example, there can be class-level static variables being used to store values. This is not an issue for most .NET programming, but the model in SQL Server’s CLR-integration is to create a single App Domain (per database, per assembly owner) that is shared across all sessions. The top-level classes are not instantiated and all methods exposed to SQL Server must bestatic
. Sharing the same memory and objects (i.e. the class(es)) means that the static class variables are also shared across sessions, hence one session can read and overwrite values in use by another session. This is typically not how such code was intended to be used and testing by one person will not expose any issues. But as soon as multiple processes are using that same code at the same time, it is quite possible that your code will appear to have some bugs that are really hard to find. So if you do use unsupported .NET Framework libraries, your regression tests really need to have multiple connections hitting that functionality. - DLLs updated when installing a new version of .NET may need to be reloaded and your code might need to be recompiled and reloaded:
As stated in Microsoft Knowledge Base article 949080 (see “Additional Reading” section at the end):
When the CLR loads an assembly, the CLR verifies that the same assembly is in the GAC. If the same assembly is in the GAC, the CLR verifies that the Module Version IDs (MVIDs) of these assemblies match. If the MVIDs of these assemblies do not match, you receive the error message that the "Symptoms" section mentions.
When an assembly is recompiled, the MVID of the assembly changes. Therefore, if you update the .NET Framework, the .NET Framework assemblies have different MVIDs because those assemblies are recompiled. Additionally, if you update your own assembly, the assembly is recompiled. Therefore, the assembly also has a different MVID.
This means that if you incorporate an unsupported Framework assembly and that assembly is updated in a later version of the .NET Framework, your SQLCLR code will stop working until you reload that assembly.
- DLLs updated when installing a new version of .NET might not be loadable into SQL Server anymore if they switch to mixed format (i.e. not “pure”). Being unsupported means that Microsoft has no obligation to make sure they continue to work inside SQL Server:
There are two types of .NET assemblies: “pure” and “mixed”. Pure assemblies are only managed code, while mixed assemblies also contain unmanaged C++ code. SQL Server only allows pure assemblies to be loaded via CREATE ASSEMBLY. This is why you can only reference some, but not all, of the unsupported Framework DLLs. And to complicate life just a little bit, it is possible for an assembly to go from pure to mixed in a new version of the .NET Framework. This means that any unsupported assembly that you successfully incorporate today could change in such a way that it can no longer be loaded, and reloading and recompiling will not be an option. If this happens (and it, in fact, has happened) then you will have to find a substitute, or write that method yourself, or something else. Either way, it could amount to a lot of extra work. However, the supported libraries are guaranteed to work across new versions of the Framework.
Framework Versions and Supported Libraries and Unsupported Libraries, oh my!
Let’s look at an example that puts all of this together. .NET 4.5 introduced some improvements to the System.IO.Compression namespace to allow for easily zipping up files in a folder into a single zip file. We have been able to use the GZipStream class in SQLCLR projects since SQL Server 2005 / .NET 2.0 so there should be no problem using ZipFile.CreateFromDirectory as long as our assembly is loaded into SQL Server 2012, 2014, or anything newer, right? Not so fast. While the GZipStream class and the ZipFile class are both in the System.IO.Compression namespace, GZipStream is located in System.dll (in the supported libraries list) while ZipFile is located in System.IO.Compression.FileSystem.dll (a new DLL that is not in the supported libraries list). In order to get this to work we would need to load System.IO.Compression.FileSystem into SQL Server:
CREATE ASSEMBLY [System.IO.Compression.FileSystem] FROM N'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.IO.Compression.FileSystem.dll' WITH PERMISSION_SET = UNSAFE;
This will automatically import System.IO.Compression.dll as well since System.IO.Compression.FileSystem references it. That wasn’t so bad.
We test it on the development server and everything works as expected. Now we can publish our project to the test server. We create our assemblies from hex codes (i.e. the FROM 0x455A90...
option) instead of DLLs as it is easier to just have a SQL script to deploy. But we get an error when attempting to create the two unsupported Framework assemblies: they rely on updated versions of some of the supported Framework assemblies, but those don’t exist on this server as it was never upgraded to .NET 4.5.
So now we have three additional requirements when we load our assembly into a new system:
- Any database that our assembly is loaded into needs to be altered with:
SET TRUSTWORTHY ON
- Any database that our assembly is loaded into needs to get the System.IO.Compression and System.IO.Compression.FileSystem DLLs
- Any instance our that our assembly is loaded into needs to have .NET Framework 4.5 installed
Data Types
Data types between systems (databases, languages, etc) are not always the same, even if they have the same name. Mappings are usually provided to translate between the systems. In .NET there is a set of classes in the System.Data.SqlTypes
namespace that map directly to most of the SQL Server datatypes (see the “SQL Server Data Type Mappings” link in the “Additional Reading” section). As stated on that “SQL Server Data Type Mappings” page:
Each data type in SqlTypes has its equivalent data type in SQL Server, with the same underlying data representation. Many of them also have equivalent data types in the CLR. However, SqlDateTime, SqlDecimal, and SqlString have different underlying data structures with their corresponding .NET Framework data types.
Benefits
As long as there is a mapped SqlType available, you should use those types as input and output parameters and return values instead of the standard .NET types (e.g. use SqlString instead of string). There are several good reasons for using the SqlTypes. The following list of benefits is taken from “Nullability and Three-Value Logic Comparisons” link in “Additional Reading” section:
NULL Values
A primary difference between native common language runtime (CLR) data types and SQL Server data types is that the former do not allow for NULL values, while the latter provide full NULL semantics.
Comparisons are affected by NULL values. When comparing two values x and y, if either x or y is NULL, then some logical comparisons evaluate to an UNKNOWN value rather than true or false.
SqlBoolean Data Type
The System.Data.SqlTypes namespace introduces a SqlBoolean type to represent this 3-value logic. Comparisons between any SqlTypes return a SqlBoolean value type. The UNKNOWN value is represented by the null value of the SqlBoolean type. The properties IsTrue, IsFalse, and IsNull are provided to check the value of a SqlBoolean type.
Operations, Functions, and NULL Values
All arithmetic operators (+, -, *, /, %), bitwise operators (~, &, and |), and most functions return NULL if any of the operands or arguments of SqlTypes are NULL. The IsNull property always returns a true or false value.
Precision
Decimal data types in the .NET Framework CLR have different maximum values than those of the numeric and decimal data types in SQL Server. In addition, in the .NET Framework CLR decimal data types assume the maximum precision. In the CLR for SQL Server, however, SqlDecimal provides the same maximum precision and scale, and the same semantics as the decimal data type in SQL Server.
Overflow Detection
In the .NET Framework CLR, the addition of two very large numbers may not throw an exception. Instead, if no check operator has been used, the returned result may "wrap around" as a negative integer. In System.Data.SqlTypes, exceptions are thrown for all overflow and underflow errors, and divide-by-zero errors.
Additional benefits include:
Streamed access to LOB parameters
Streaming values in can be achieved via SqlChars for N[VAR]CHAR, SqlBytes for [VAR]BINARY, and SqlXml.CreateReader() for XML. While SqlChars and SqlBytes work for NCHAR / NVARCHAR and BINARY / VARBINARY, respectively, of any length, they are primarily intended to provide access to large values that is faster and uses less memory than the SqlString and SqlBinary types. This efficiency is achieved by providing access to a stream of the data via the
Read()
method that can read in chunks of 1 or more characters / bytes at a time instead of copying the entire value into memory like SqlString and SqlBinary do.The streamed access of SqlChars and SqlBytes has a secondary benefit, in addition to already not requiring the value's full size of memory: it might not require much of any memory at all if the value is never accessed (well, 8 kb). Unlike SqlString and SqlBinary which transfer the full value of the input param as the SQLCLR method is being called, SqlChars and SqlBytes lazy load their values (i.e. not until requested). This means that if the method exits before requesting any of the value from these two types, then no data was ever sent over, and this saves time (both CPU and Elapsed) as well as memory (for strings of 1980 or more characters).
Insight into the collation
When using SqlString (not string or even SqlChars) you can access the CompareInfo, CultureInfo, LCID, and SqlCompareOptions properties (see the “SqlString properties” link in the “Additional Reading” section for details). Of course, this is the database default collation and not the collation of a particular field or inline specification. So there is no way to tell if a value passed in is of a collation that is not the same as the database default, but in those instances when you are sure that the collation is the database default, then this can be quite handy.
Exceptions
As previously mentioned, the SqlTypes do not map all of the SQL Server data types. Below is a list of the exceptions. Please note that a "?" at the end of a Type, as in Type?
, is just short-hand for Nullable<Type>
(see the "Nullable Types (C# Programming Guide)" link in the "Additional Reading" section for full details).
- CHAR / VARCHAR
- No mappings as .NET is entirely UCS-2. All strings are NCHAR / NVARCHAR
- SQL_VARIANT maps to Object
- test NULL via
object == DbNull.Value
- Use only when necessary. This is not a magical work-around for not being able to do overloading: there is a noticeable performance penalty for using this datatype.
- LOB types (NVARCHAR(MAX), VARBINARY(MAX), and XML) cannot be sent through a SQL_VARIANT parameter.
- test NULL via
- DATETIME2
- Use .NET
DateTime
orDateTime?
- Use .NET
- DATETIMEOFFSET
- Use .NET
DateTimeOffset
orDateTimeOffset?
- Use .NET
- TIME
- Use .NET
TimeSpan
orTimeSpan?
- Use .NET
Good to Know
The following information are things that one should know about working with the SqlTypes in order to be successful and avoid certain pitfalls.
Each of the SqlTypes classes has various static and instance methods and properties (see the "System.Data.SqlTypes Namespace" link in the "Additional Reading" section for full details). While many of the methods and properties vary between the different SqlTypes, there are a few that are consistent across them and are used quite frequently:
Value
: This instance property returns the underlying value as the associated .NET type. A SqlInt32 returnsint
, a SqlBoolean returns abool
, and so on. There is no need to cast a SqlType to a .NET type, nor useToString()
on a SqlString.IsNull
: This instance property is used to test for the value being "NULL", as opposed to using either "== null
" or "== DBNull.Value
".Null
: This static field is used to send a "NULL" back to the database, as opposed to using either "null
" or "DBNull.Value
".
And there are a few minor differences between the .NET side and the SQL Server side for some of the types that do have mappings.
- UNIQUEIDENTIFIER maps to
SqlGuid
, which is mostly the same as a .NET Guid. The difference is in how they compare to other guids. The MSDN page for "Comparing GUID and uniqueidentifier Values" (see link in the "Additional Reading" section) states:Both Guid and SqlGuid have a CompareTo method for comparing different GUID values. However, System.Guid.CompareTo and SqlTypes.SqlGuid.CompareTo are implemented differently. SqlGuid implements CompareTo using SQL Server behavior, in the last six bytes of a value are most significant. Guid evaluates all 16 bytes.
- DECIMAL (and hence NUMERIC) maps to
SqlDecimal
, but the datatype on the database side can actually hold values with a greater precision than in .NET (i.e. max of 38 instead of 28). So if you are passing in DECIMAL values and need to do arithmetic then you might need to use the SqlDecimal methods, such as Add and Multiply, etc. Also, when left unspecified, the default Precision and Scale used for input and output parameters generated by Visual Studio / SQL Server Data Tools -- both are discussed in the next level -- is (18, 0). You can use[SqlFacet(Precision = x, Scale = y)]
to set custom values. - NVARCHAR(1 - 4000) and NVARCHAR(MAX) map to
SqlString
andSqlChars
respectively when using Visual Studio / SQL Server Data Tools to publish your SQLCLR project. It appears to be commonly thought that you must use SqlString to map to NVARCHAR(1 - 4000) and SqlChars to map to NVARCHAR(MAX). However, this is not a requirement; these mappings just happen to be the default used by those two tools.You should decide between SqlString and SqlChars based on several factors:
- If the value will be frequently sent but not accessed / used, use SqlChars, regardless of size
- If the value can be accessed in pieces (e.g. IndexOf, LastIndexOf, saving to a FileStream, etc) AND is at least 45,000 characters, use SqlChars and the
Read()
method - If the entire value is required for a particular operation (e.g. RegEx, passing to a method as a String parameter, etc) OR will always be less than 45,000 characters, then use SqlString
- For return values (scalar functions) and table fields (table-valued functions), use SqlString as it is faster and uses less memory
You should decide between NVARCHAR(1 - 4000) and NVARCHAR(MAX) based on the potential size of that value. You can control what Visual Studio / SQL Server Data Tools will auto-generate via
[SqlFacet(MaxSize = x)]
where "x" is either 1 - 4000 or -1 to represent "MAX". When creating SQLCLR functions (scalar or table-valued), be sure to only useMaxSize = -1
/ NVARCHAR(MAX) when the value needs to hold more than 4000 bytes. The reason for this caution is that if a MAX type is used anywhere (input param, return value, or field in a result set), there will be a performance penalty. It will be small enough to not really be noticed on a function running once per minute (or less frequently), but it could easily be noticeable on a function running many times per second.There is, unfortunately, a general lack of information available regarding the true differences between
SqlString
andSqlChars
with respect to memory usage and performance. However, this situation will soon be corrected as a future article will provide a detailed analysis so that this issue can be fully understood.
- SMALLDATETIME and SMALLMONEY do not have explicit mappings to any SqlTypes, but can use
SqlDateTime
andSqlMoney
, respectively. If you are using Visual Studio / SQL Server Data Tools to publish your SQLCLR project, then you will have to manually ALTER the T-SQL wrapper object(s) to change the auto-mapped datatypes of DATETIME (forSqlDateTime
) and MONEY (forSqlMoney
) into SMALLDATETIME and SMALLMONEY, respectively.
Examples
The example code—in a zip file (StairwayToSQLCLR-05-ExampleCode.zip) attached to this article at the bottom in the “Resources” section—consists of:
- A folder named “Level-05” containing:
- Six SQL scripts
- A subfolder named “Assemblies” containing:
- Two DLLs
- A subfolder named “SourceCode” containing:
- Two C# .cs files
Example Setup
If you have not already done so via prior “Stairway to SQLCLR” level examples, please create a C:\TEMP\StairwayToSQLCLR folder either in Windows Explorer or from a command prompt with the following command:
MKDIR C:\TEMP\StairwayToSQLCLR
Extract the contents of the zip file, starting with the “Level-05” folder itself, into C:\TEMP\StairwayToSQLCLR such that you now have C:\TEMP\StairwayToSQLCLR\Level-05\... .
For those who want to play with the C# source code, there are two files in the C:\TEMP\StairwayToSQLCLR\Level-05\SourceCode folder. The Visual Studio solution file and project files that contain the configuration for building and signing the source code are not included; the purpose of this article and the examples contained herein is to gain a better and clearer understanding of how .NET works inside of the SQL Server environment, not how to build SQLCLR code. The focus here needs to stay on studying the behavior and we will start actual development in the next level. Along those lines, the C# source code will only be explained in brief.
Run the first script, StairwayToSQLCLR-05-01-CreateDatabase.sql, even if you already have the test database, [StairwayToSQLCLR]
, created from prior level examples, and then close it.
Run the second script, StairwayToSQLCLR-05-02-EnableCLR.sql, and then close it. This script will enable “CLR Integration” if it is not already enabled. It will also create a User-Defined Function in the [StairwayToSQLCLR]
database which returns a BIT
value denoting whether or not this script enabled “CLR Integration” or not. This function, [DidWeEnableCLR]
, is used in the cleanup script, StairwayToSQLCLR-05-05-DisableCLR.sql, as a means of determining whether or not to actually disable “CLR Integration”. If “CLR Integration” was enabled prior to running the StairwayToSQLCLR-05-02-EnableCLR.sql script, then the [DidWeEnableCLR]
function will return 0 and “CLR Integration” won’t be disabled as it might be in use for something other than these examples.
Supported Library Test
Attempt to use the TimeZoneInfo
class, which was introduced in .NET Framework version 3.5. Creating the Assembly should only error on SQL Server 2005 as the class is part of System.Core.dll which was included as a supported library starting in SQL Server 2008. That this works in 2008 but not 2005 proves that a) you are not restricted to .NET Framework 2.0 functionality in SQL Server 2005 / 2008 / 2008 R2, and b) that regardless of any new functionality provided in any updates to the .NET Framework, anything not found in one of the supported libraries is still unavailable (at least without manually importing one or more unsupported libraries).
The code shown below simply converts the local server time to "Tokyo Standard Time". The important aspect of the code is just the fact that it uses the TimeZoneInfo
class, which requires adding a reference to System.Core
. That reference will be checked when loading the assembly and if System.Core is not found then the CREATE ASSEMBLY will fail.
Open the StairwayToSQLCLR-05-03-SupportedLibraryTest.sql script. Hit F5 / Control-E. Unless you are running this on SQL Server 2005, you should see output in the "Messages" tab, ending with:
Time in Tokyo: xxxxxxxxx
Running this on SQL Server 2005 will result in the following error:
Msg 6503, Level 16, State 12, Line 1
Assembly 'system.core, version=3.5.0.0, culture=neutral, publickeytoken=b77a5c561934e089.' was not found in the SQL catalog.
Framework Version Test
Use GZipStream
to compress some text. This class is available in System.dll
and so has been available to use starting in SQL Server 2005. Compress a specified string and it will always have a size of X bytes when using SQL Server 2005 / 2008 / 2008 R2. But when using SQL Server 2012 / 2014, it will be that same size of X bytes only if the system has not yet been updated to .NET 4.5. The GZipStream compression algorithm used in .NET Framework versions 2.0, 3.0, 3.5, and 4.0 was not very good. This was fixed in .NET 4.5, so when using SQL Server 2012 / 2014 on a system that has had its .NET Framework updated, the size of that same string will be much smaller than X bytes. This proves that a) SQL Server 2012 / 2014 will automatically get enhanced .NET functionality through regular updates of the .NET Framework (so long as those updates run on CLR version 4.0) and that b) SQL Server 2005 / 2008 / 2008 R2, regardless of updates to the .NET Framework, cannot access newer functionality beyond Framework version 3.5 (since they are bound to Framework versions that run on CLR version 2.0).
The code shown below writes the incoming binary data (in DataToCompress
) to the GZipStream which stores the compressed value in the MemoryStream. The data in the MemoryStream is used to create a new SqlBytes variable to pass back. Please note the use of the IsNull
property and the static Null
field in that first "if" block.
Open the StairwayToSQLCLR-05-04-FrameworkVersionTest.sql script. Hit F5 / Control-E. It installs the assembly and runs:
SELECT dbo.GZip(CONVERT(VARBINARY(4000), N'OMG! This is such a test!'));
If you are running this on SQL Server 2005 / 2008 / 2008 R2, or 2012 / 2014 on a server has not been updated with at least .NET Framework 4.5, you will get the following returned (170 bytes):
0x1F8B0800000000000400EDBD07601C499625262F6DCA7B7F4AF54AD7E074A10880601324D8904010ECC188CDE692EC1D69472329AB2A81CA6556655D661640CCED9DBCF7DE7BEFBDF7DE7BEFBDF7BA3B9D4E27F7DFFF3F5C6664016CF6CE4ADAC99E2180AAC81F3F7E7C1F3F22BEFC35BEF8353EFF357ED75F23FD35DEFC1AF35FA3F8351AFA4DFE6D7E8DF5AF31A5CFD25F23A3FFB7BF464E9FB4BFC6EFFA6BFC3F019BA23D32000000
But, if you are running this on SQL Server 2012 / 2014 IF the server has been updated with at least .NET Framework 4.5, you will get the following returned (59 bytes):
0x1F8B0800000000000400F367F0657067506450600861C860C8642806B220643143294332504C812111884B1852812225409500019BA23D32000000
Example Cleanup
If you want to put the system back to the state it was in prior to running any of the example scripts, run scripts 5 and 6 in order. Doing so will remove all of the artifacts created by the example scripts (Database, Asymmetric Key, and Login), and disable "CLR Integration", but only if it was enabled in script 2.
Summary
This time we looked at the various factors that influence what we are able to do with SQLCLR and how data types are managed between .NET and SQL Server. We distinguished between the CLR and the .NET Framework and explored the relationship between their various versions and the different versions of SQL Server. In the next level we will start coding with Visual Studio and SQL Server Data Tools.
Additional Reading
Environment:
- CLR and Framework versions:
- Common Language Runtime (CLR): http://msdn.microsoft.com/en-us/library/8bs2ecf4.aspx (see chart in “Versions of the Common Language Runtime” section)
- .NET Framework version history: http://en.wikipedia.org/wiki/.NET_Framework_version_history
- SQL Server 2008 R2 - SQLCLR .NET Framework Version: http://blogs.msdn.com/b/dohollan/archive/2010/07/08/sql-server-2008-r2-sqlclr-net-framework-version.aspx
- SQL Server 2012 - SQLCLR .NET Framework Version: http://blogs.msdn.com/b/dohollan/archive/2012/04/20/sql-server-2012-sqlclr-net-framework-version.aspx
- Hardware and Software Requirements for Installing SQL Server : http://msdn.microsoft.com/en-us/library/ms143506.aspx
- Supported .NET Framework Libraries
- SQL Server 2005: http://technet.microsoft.com/en-us/library/ms403279(v=sql.90).aspx
- SQL Server 2008 / 2008 R2 / 2012 / 2014: http://msdn.microsoft.com/en-us/library/ms403279.aspx
- Support policy for untested .NET Framework assemblies in the SQL Server CLR-hosted environment : https://support.microsoft.com/kb/922672
- Error message when you execute a CLR routine or use an assembly in SQL Server: "Assembly in host store has a different signature than assembly in GAC. (Exception from HRESULT: 0x80131050)" https://support.microsoft.com/kb/949080
- Data Types:
- Nullability and Three-Value Logic Comparisons: http://technet.microsoft.com/en-US/library/ms131081.aspx
- Collation and CLR Integration Data Types: http://technet.microsoft.com/en-us/library/ms131091.aspx
- SQL Server Data Type Mappings (all versions): http://msdn.microsoft.com/en-us/library/cc716729.aspx
- SQL Server Data Types and Their .NET Framework Equivalents (2005): http://technet.microsoft.com/en-us/library/ms131092(v=sql.90).aspx
- SQL Server Data Types and Their .NET Framework Equivalents (2008 / 2008 R2 / 2012 / 2014): http://technet.microsoft.com/en-us/library/ms131092.aspx
- System.Data.SqlTypes Namespace: http://msdn.microsoft.com/en-us/library/system.data.sqltypes.aspx
- SqlString Properties: http://msdn.microsoft.com/en-us/library/System.Data.SqlTypes.SqlString_properties.aspx
- SqlDecimal Structure: https://msdn.microsoft.com/en-us/library/system.data.sqltypes.sqldecimal.aspx
- Comparing GUID and uniqueidentifier Values: https://msdn.microsoft.com/en-us/library/ms254976.aspx
- Handling Null Values: https://msdn.microsoft.com/en-us/library/ms172138.aspx
- Nullable Types (C# Programming Guide): https://msdn.microsoft.com/en-us/library/1t3y8s4s.aspx
- Memory:
- SQL Server 2005 / 2008 / 2008 R2: http://mssqlwiki.com/sqlwiki/sql-performance/basics-of-sql-server-memory-architecture/
- SQL Server 2012 / 2014: http://mssqlwiki.com/tag/sql-server-2012-memory-architecture/
Changes:
- What's New in CLR Integration 2012 / 2014: http://msdn.microsoft.com/en-us/library/gg471528.aspx
- Programmability Enhancements (2008 / 2008 R2): http://msdn.microsoft.com/en-us/library/cc645577(v=sql.105).aspx
- Behavior Changes to Database Engine Features (2014): http://msdn.microsoft.com/en-us/library/ms143359.aspx
- Breaking Changes to Database Engine Features (2008): http://msdn.microsoft.com/en-us/library/ms143179.aspx#KJKatmai
Miscellaneous:
- TimeZoneInfo Class: http://msdn.microsoft.com/en-us/library/system.timezoneinfo(v=vs.90).aspx
- System.IO.Compression Namespace: http://msdn.microsoft.com/en-us/library/System.IO.Compression.aspx
- ZipFile.CreateFromDirectory Method: http://msdn.microsoft.com/en-us/library/hh485721.aspx
- ALTER DATABASE SET Options: http://technet.microsoft.com/en-us/library/bb522682.aspx (in relation to the TRUSTWORTHY external access option)
- CREATE ASSEMBLY: http://technet.microsoft.com/en-us/library/ms189524.aspx
About the Author
Solomon Rutzky has been working with databases for 19 years and specifically with SQL Server for 13 years. He has written several articles for SQL Server Central and Simple-Talk. Notable companies that he has worked for include PLATINUM technology and ChannelAdvisor. Solomon is also the author of the popular SQLCLR library of functions: SQL# (SQLsharp).