|
Creating User Defined Types (UDTs)
|
|
This article is
based on the released versions of SQL Server 2005 and VS 2005. The code samples in this article have been suitably wrapped for readability. During compiliation, you will have to bring them back to one line wherever applicable.
At some point of time, we as developers feel the need that the standard data types provided by an application is not sufficient to model our application. For example, if an application supports data types like integer, decimal etc, sometimes we might need to create a different data type to hold some complex piece of information. In the development languages world like C#, VB.NET etc, new data types are created by modelling them as classes or structures. For example, you can create a data type to hold geospatial information for your application and then use that data type as though it was a regular data type (through features like operator overloading etc). Although this is great from a programming languages point of view, what about the case when you want to do the same in a database (especially SQL Server)? Can we create new data types in the database? SQL Server 2000 had very limited support for extending the type system. I would not call it as "extending" the type system in SQL Server 2000, since the feature available basically allowed you to only "alias" an existing data type as a new data type with some associated rules. To understand what SQL Server 2000 supported, consider the case wherein we want to create a data type to hold the gender values for a person. The rules for this data type are pretty simple. The gender needs to hold three values (Male, Female, Undefined) and has to have a width of 1 character. Here is what you would do in SQL Server 2000 to model this:
We start off by creating a rule called GenderRule that allows only permissible values. We then create a new user-defined alias type called Gender and then bind the rule to the type using the sp_bindrule command. Once this is done, we can use this data type in regular T-SQL expressions as follows:
Note that we can use the type that we created as though it was a regular data type in CREATE statements, procedure parameters, variable declaration etc. Once used, we can insert only the permissible values into that particular column. Why did SQL Server 2000 have this capability? One simple reason is to allow people to create standard business terms as data types and then enforce standardization across the entire application. Say for example, we did not have a data type called Gender, then across the system, people could have create either a CHAR or a VARCHAR type and associated possibly different rules to the same. Rather, by creating the user-defined type, we can have the entire application use this type across the board and ensure standardization. Although this feature is really useful, it still has limitations. You can only create alias types for existing SQL Server types. You cannot extend the type system of SQL Server. For example, you cannot create a type to hold latitudes and longitudes. This is where SQL Server 2005 steps in and allows you to truly extend the type of the database. It makes this possible through the CLR integration capabilities. In this article, we will discuss on how we can create a new user-defined type in SQL Server 2005 and then use it as a normal data type. Implementing a User Defined Type Implementing a user defined type is quite simple in SQL Server 2005. The following are the high-level steps that you need to follow:
To illustrate each of the points above, in this article, we will build a user-defined type called ComplexNumber. All of us, during our earlier mathematics days, would have heard about complex numbers. A complex number has a real part and an imaginary part and is of the syntax a + bi or a - bi. Once you have a complex number, you can then add and substract another complex number from it. If you need more information on complex numbers, you can refer to this link: Complex Numbers. Note that as part of this example, we will implement only addition and subtraction of complex numbers. Creating the assembly for the UDT As the first step, let us create the assembly for the UDT. For this, you need to create a new C# Database project in Visual Studio 2005. Name the project as ComplexNumber and also choose the database in which you wish to deploy the UDT. Once you have created the project, right click the project and choose Add > User Defined Type. Name the user defined type as ComplexNumber. Once you have created the type, you can see that Visual Studio fills out a major portion of the code with template information that you then need to complete. You will also notice that your class implements INullable. This is required to support the concept of nullability. UDTs loaded into SQL Server and aware of NULLs, but for the UDT itself to recognize a null value, we need to implement this interface. First, let us define the namespaces that we will need for our complex number project. Apart from the list of namespaces that are already added, we need a few more. Here is the full list that we will need.
Next, notice that an attribute has been added above your class. The attribute definition that we need should look like the following:
The [Serializable] attribute is optional, but you will need the SqlUserDefinedType attribute. This attribute basically specifies the storage options for the UDT. For our example, we need two settings:
Next, let us declare a few variables to be used within the program.
To implement nullability behavior, you need to create a property called IsNull. This property is used by SQL Server to determine if the UDT is indeed NULL. If so, then the default behavior of NULLs that SQL Server implements will take over. This means that, for example, you can use the IS NULL construct to check if the UDT is null. Apart from the IsNull property, you should also implement a static property called Null that returns a null instance of the UDT. This allows the UDT to return a null instance if the value is indeed NULL in the database. Here is the source for these two properties.
The next important method to implement is the Parse method. The Parse method is called to convert a string representation into a UDT and must be a static method. Here is the source for the same.
Some points to note. Note that in the Parse method, we first check to see if the string is null. If so, we just return back. Now, based on our definition of the structure of a complex number, we create a regular expression pattern and then match the incoming string to that pattern. If the pattern matches, we extract the real part and imaginary part, otherwise, we throw an exception back to the user. Note that in our implementation of a complex number, the format is a+bi without any spaces inbetween. We have also added an attribute on top of this method that prevents this method being called with a null instance of the UDT. Ok, we now have a method to convert strings to complex numbers, but what about the other way around? For example, what if we need a string representation of our complex number? For this, we need to implement the ToString method which just does the opposite of the Parse method. Here is the source for the same.
The implementation is pretty simple. We just use a string builder object to build a string representation of our complex number and then return it back. We can also expose the components of the complex number as a set of properties as shown.
These two properties will allow anyone using our complex number to set the components of the complex number independently. Actually, this is it! Our UDT is fully functional now, but for the sake of completeness, we will implement two methods that will allow users to add and subtract complex numbers. Here is the source for the two methods.
Ok. Now that we are completed in terms of coding for the user-defined type, we can start deploying the UDT in the database. Registering and Creating the UDT To register the assembly, we will need to use the CREATE ASSEMBLY command and to create the type we need to use the CREATE TYPE command. Here are the commands for the same. Note that in the example below, I've given the path to the DLL in multiple lines, but before you execute it, make sure that it is on the same line . I did it to avoid horizontal scrolling of the article.
Once the above commands are successful, we are ready to use the complex number UDT. Note that if you use Visual Studio's auto deployment feature, the above commands are automatically done for you. To do this, right-click the project and choose Deploy. This will deploy the UDT onto the database connection that you gave when you created the project.
In the above example, we create a variable of our UDT and then play around with the various methods that we created. Fun, isn'nt it? Here is another example that works with the other methods that we created.
If you want to simulate the exception that we raised in the code, you can try the following example.
Till now, we have seen how to define and consume user defined data types in the database. Now, let us see how to consume them in the client application tier using ADO.NET. Consuming User Defined Types in ADO.NET Consuming user-defined types on the client side is quite easy in ADO.NET. You can both consume UDTs from the database and also send them as parameters to the database. ADO.NET provides a new parameter type called Udt that allows you to pass-in UDT values. To read UDT values, you will need to type-cast the values from the database into the UDT type and then use the same. Alternatively, you can also use the binary stream as it is from the database. To do all of these, the only requirement for using UDTs in the client application tier is that the client application must have access to the assembly that implements the UDT. To provide access, you can just make a reference from your client application to the UDT assembly. To illustrate this concept, let us create a simple command-line application that inserts 10 complex numbers into a table and then reads them back. Here is the source for the same:
You can see that the program is broken into 3 sections:
Once you execute the above program, you should see the following output.
Ok, that brings me to the end of this article on how to create and use the new user-defined type (UDT) to build some really interesting applications. The UDT feature allows you to truly extend the type system of SQL Server to handle some intricate and complex data types. In this article, we have used the native serialization mechanisms for UDTs which should suffice for most cases. However, sometimes, you may want to implement your own serialization. This will be an example that I will post onto my resources section. You can also refer to the SQL Server books online for more examples. Do send me your feedback and comments to srisamp@gmail.com. |
| Home |