六 用户定义的类型 (UDT)
现在,我们来讲 SQL Server 2005 中功能更强大但是经常被错误理解的一个功能。使用用户定义的类型 (UDT),可以扩展数据库的标量类型系统(不仅仅为系统类型定义您自己的别名,这在 SQL Server 以前的版本中一直可用)。定义 UDT 就像用托管代码编写类,创建程序集,然后使用“create type”语句在 SQL Server 中注册该类型一样简单。下面是实现 UDT 的主干代码:
[SqlUserDefinedTypeAttribute(Format.Native)]
public struct SimpleUdt: INullable
{
public override string ToString() {...}
public bool IsNull { get; }
public static SimpleUdt Null { get; }
public static SimpleUdt Parse(SqlString s) {...}
...
}
create type simpleudt from [myassembly].[SimpleUdt]
create table t (mycolumn simpleudt)
何时创建 UDT
SQL Server 2005 中的 UDT 不是对象相关的扩展性机制。它们是扩展数据库的标量类型系统的一种方法。标量类型系统包括 SQLServer 附带的列类型(如 int、nvarchar 和 uniqueidentifier 等类型)。例如,使用 UDT,可以定义您自己的、用于列定义的类型。如果您的类型确实是一个适合建模为列的原子值,请创建 UDT。
如果需要定义您自己的标量类型,请使用 UDT。这种类型的示例情况包括各种日历中的自定义日期/时间数据类型和货币数据类型。使用 UDT,可以创建单个对象来公开类型上可用的所有行为,并且封装或隐藏该类型所存储的基础数据。需要访问数据的每个人都必须使用 UDT 的编程接口。如果能够利用 .NET Framework 中现有的功能(如国际化或日历功能),这实际上又是考虑将类型实现为 UDT 的一个很好的理由。
何时不创建 UDT
不要使用 UDT 来对复杂的业务对象(如雇员、联系人或客户)进行建模。您将会陷入 UDT 的所有列限制(如,8KB 大小限制、索引限制)和在更新 UDT 值时更新整个值的不利方面。对于复杂类型,UDT 不是合适的数据建模抽象;因此对于这种情况,最好使用中间层对象相关映射技术。
设计 UDT 时需要考虑的因素
因为它们是列形式的,所以可以定义整个 UDT 值的索引,并且创建参考完整性约束(如 UDT 列的唯一性)。还可以在比较和排序方案中使用 UDT。
比较 UDT 值是通过比较类型的基础二进制表示完成的。如果使用 Format.Native 作为持久性机制,则会按照同在该类型中定义的一样的字段顺序创建永久形式;因此,请确保这些字段是按照类型的正确顺序排列的。
UDT 上的每个操作(除了比较)都要求 UDT 的值反序列化,接着进行方法调用。这种模式有与之相关的固定开销。如果要将类型建模为 UDT(相对于表中的列),则在访问类型属性(相对于表中的列)时应该考虑这种差别。如果类型上的行为非常复杂,则应该考虑使用 UDT。如果类型没有任何与之相关的行为,则应该考虑将数据存储为表中的列。
如果发现需要实现相关函数的库,则 UDT 中的静态方法是一种可以方便地创建这种库的封装机制。可以通过使用 :: 语法来调用T-SQL 中的静态方法,如下所示:
select imagetype::MyFunction(@arg1)
实际方案
客户希望在 UmAlQuraCalendar 中存储日期时间值,这与 SQL Server 日期时间数据类型使用的 Gregorian 日历不同。他们想让这个日期类型具有相同的基本行为集,即字符串转换、日期部分、日期算法和 GetDate()。
下面是这一数据类型的代码片段。它使用 UmAlQuraCalendar 2.0 版,这是 .NET Framework 中的新类型。
[SqlUserDefinedType(Format.Native, IsByteOrdered = true)]
public struct UmAlQuraDateTime : INullable
{
//Private state
private long dtTicks;
//Calendar object used for all calendar-specific operations
private static readonly UmAlQuraCalendar s_calendar = new UmAlQuraCalendar();
public static UmAlQuraDateTime Null
{
get
{
UmAlQuraDateTime dt = new UmAlQuraDateTime();
dt.isNull = true;
return dt;
}
}
public bool IsNull
{
get
{
return this.isNull;
}
}
//Convert into CLR DateTime type
public DateTime DateTime
{
get { return new DateTime(this.dtTicks); }
}
字符串转换和创建新值:
public static UmAlQuraDateTime Parse(SqlString s)
...
public override string ToString()
...
public static UmAlQuraDateTime ParseUsingFormat(string data, string fmt)
...
public string ToStringUsingFormat(string format)
...
public static UmAlQuraDateTime Now
{
get
{
return new UmAlQuraDateTime(DateTime.Now);
}
}
public static UmAlQuraDateTime FromSqlDateTime(SqlDateTime d)
{
if (d.IsNull) return Null;
return new UmAlQuraDateTime(d.Value);
}
]
