SQL Server 2005 has the .NET Framework embedded within it, which allows the use of managed languages for creation of objects such as stored procedures, function, aggregates, and types. This book is not dedicated to SQLServer 2005, so most of these topics are not covered, but the use of userdefined types (UDTs) affects client applications such as ASP.NET pages, so is worthy of coverage here. By "custom types," we mean any object defined within .NET. SQL Server already has support for native types, such as string, dates, numbers, and so on, but there may be other types you want to store. For example, you might want to store dates and times as separate columns, or perhaps you have a more complex object such as Longitude or Latitude for a mapping operation. You can create these UDTs as .NET structures and store them within SQL Server 2005 as a native typeso the column type is your custom type. Listing 5.24. Using Asynchronous Tasks
Let's take a look at location-based storage with Longitude and Latitude classes; with the increasing popularity of GPS systems, especially among sports enthusiasts (see http://www.expansys.com/p_sportsdo.asp for some cool PocketPC applications), and the uptake of free mapping services such as Virtual Earth, the storage of trip information can provide a great way to document your activities. Using SQL Server 2005, you can natively store the GPS details as a UDT, allowing them to be easily used in mapping applications. A UDT is simply a structure in .NET that follows a set of rules:
Other properties and methods can be implemented, depending upon the requirements of the UDT. For the Latitude type, you could store the latitude in a decimal format, along with properties for Degrees, Minutes, Seconds, and the Hemisphere. This is shown in Listing 5.25, which details the minimum requirements for a SQL UDT. An equivalent UDT for Longitude is included in the downloadable samples, but is not discussed in depth; you'll see that it is very similar to the latitude. Let's break this code down to see exactly what each section does, and why. UDT AttributesThe first thing to notice is that the structure is attributed, first with Serializable; all UDTs must be serializable, because SQL Server serializes the data for storage internally in the database. The second attribute, SqlUserDefinedType, does two things. First, it defines that this class is a UDT, which Visual Studio uses when deploying the assembly. Second, it defines characteristics of the type. The two in use here are Format. Native, which indicates that only native types are used and that SQL Server can perform the serialization, and IsByteOrdered, which indicates that the serialized data can be used for ordering and comparison. Listing 5.25. The Latitude User Defined Type
The SqlUserDefinedType attribute supports the following properties:
Serializable TypesOne important point to notice is the types you can use as fields, which must be serializable types. This means that your fields must be one of the following types: bool, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, SqlByte, SqlInt16, SqlInt32, SqlInt64, SqlDateTime, SqlSingle, SqlDouble, SqlMoney. Take a look at the internal fields used in the UDT: internal float _latitude; internal short _degrees; internal short _minutes; internal short _seconds; internal bool _hemisphere; The degrees, minutes, and seconds are as expecteda shortbecause they only store small values. The value for latitude would perhaps be stored as a decimal type, but this isn't serializable, so float has been used. You could ignore this entirely, because the latitude can easily be recalculated from the other fields, but it's been included here to make the rest of the UDT simpler. The hemisphere (which would be N or S) is also an unexpected typebool instead of string or char, because string and char are not serializable. Instead, a boolean flag is set to true for the Northern Hemisphere and false for the Southern Hemisphere. ConstructorsThe constructor of a UDT is just like the constructor of any other type, and serves to initialize the type. In this example, a decimal (as in a description of the value, rather than the data type) latitude is used as the parameter, and the internal values are calculated from that. Latitude(float latitude) { _latitude = latitude; _degrees = (short)latitude; float m = (float)_latitude - _degrees; _minutes = (short)(m * 60); float s = (float)m - _minutes; _seconds = (short)(s * 60); _hemisphere = (latitude >= 0); } Multiple constructors are allowed, so it would be perfectly possible to have the following: Latitude(short degrees, short minutes, short seconds, string hemisphere) { _latitude = DMStoDecimal(degrees, minutes, seconds); _degrees = degrees; _minutes = minutes; _seconds = seconds; _hemisphere = (hemisphere == "N"); } This is useful if the type is being used outside of SQL Server, but it is less useful within SQL because the constructor isn't used directly when values are created. Instead, the values are parsed. Parsing ValuesTo enter values into the SQL table, you use the INSERT statement, and that doesn't change just because a column is a UDT. For UDT columns, the INSERT statement calls the Parse method of the UDT, passing into it a SqlString type of the value to be inserted. Parse must then break this string apart, converting it into the internal types. public static Latitude Parse(SqlString s) { string val = s.Value.Trim().ToLower(); // if null string or the string "null" is // passed in the return a null Longitude instance if (s.IsNull || (val == "null")) return Null; float lat = float.Parse(val); if (lat < -90 || lat > 90) throw new ArgumentException("Latitude must be between -90 and 90"); return new Latitude(lat); } It is important to remember that SQL types can be NULL, so you must plan for this. The Parse method just seen takes the string value, removes any white space, and converts it to lower case before checking for a null value on the stringSqlString is not a standard string, because it can be null. The string is then converted to a float and validated before being used to return a new instance of the UDT. Outputting ValuesTo extract values from a UDT column, you specify the column name in SQL, which in turn calls the ToString method of the type. For the latitude type, this is simplejust returning a string value of the internal latitude. public override string ToString() { return _latitude.ToString(); } You can, of course, output the value in any form you like, perhaps converting it to its individual degrees, minutes, and seconds. Handling Null ValuesSQL can store null values, so the UDT must cope with this, by having two properties with defined names. The first, IsNull, indicates whether the UDT is null; in the latitude UDT, this compares an internal _degrees field with a known value that is invalid for a legal value of degrees. You could easily store a flag to indicate the null status, but that requires an additional field, so an existing field is used. The second property is Null, which returns a UDT instance, initialized with the value defined as our null value. Like IsNull, you could easily do this another way, perhaps having another constructor that takes a flag indicating that a null instance should be created. public bool IsNull { get { _latitude == float.MinValue;} } public static Latitude Null { get {return new Latitude(float.MinValue);} } Adding PropertiesProperties aren't a required part of creating a UDT, but they do allow flexibility in its use. Properties of a UDT are exactly the same as class properties. public short Degrees { get { return _degrees; } } public short Minutes { get { return _minutes; } } public short Seconds { get { return _seconds; } } public string Hemisphere { get { return _hemisphere ? "N" : "S"; } } For example, when selecting a UDT column, the ToString method is used to return the value. For example, if Lat was a column of type Latitude, you could do the following: SELECT Lat FROM TripDetails If properties are supported in the UDT, these can be added directly to the column. SELECT Lat.Degrees FROM TripDetails SQL Server doesn't use these directly, but they are useful when accessing the UDT from client applications. |