基于CLR的自定义类型
SQL Server 2005中集成了.NET与XML,因此用户可以非常方便地自定义类型。
自定义类型的理论基础
首先需要在这里讨论的是一个饱受争议的话题:在增加.NET与XML集成后,SQL Server是否变成了一个对象数据库管理系统?事实上,正是有了.NET与XML的支持,SQL Server成为了一个更加完整的关系数据库管理系统。
在关系世界中,域定义了包含的对象,关系定义了命题。利用域与关系,你能够描述整个子系统,可以说用域与关系表述现实世界是必须的以及足够的。
实际上,关系数据库是基于数据集理论来建立的,一个关系就是一个数据集,一个数据集有多个成员,其中一个可以作为主键。
首先,关系是一个数据集,刚插入、更改或删除时,实际上是用一个数据集替换了原有的数据集。
其次,在编程中,变量是一个相对于时间与空间的常量,变量的改变可以看作时间与空间上用一个常量值替换了另一个常量值。
从上面两段话中,我们可以看出一点相似之处。唯一的不同之处在于,变量可能会变得非常快,并且在内存之中只会储存很短的一段时间;而数据集在数据库中可能会储存很长一段时间。但是实际上时间的长短是相对的,内存与硬盘存储的界限也是相对的。从这层推断上来看,关系并不能等同于类,而应该等同于变量。
(另一种简单的逻辑也可以验证上述观点,假设关系等同于类,由于类可以包含子类,那么关系也应当可以包含子关系,但是实际上这是不能被数据库所接受的)
对于一个复杂域,为什么我们不能用一个独有的表述形式与运算方法去构造它呢?因为对于每个不同的人,每种不同的需要,对于同一个域的理解都可能会有所不同。因此我们需要自定义的去创建复杂域。
使用一个数据类型需要实现许多个细微的方法。而诸如T-SQL这种非过程性语言包含了太多命令块,因此需要特别主要控制与限制方法的输入。正则表达式可以非常好地完成这个任务。
创建自定义类型
我们可以以cn = a +bi为例来学习创建自定义类型
where i = square root of -1 (i.e. i * i = -1).
Basic arithmetic operations are defined as:
Addition: (a + bi) + (c + di) = (a + c) + (b + d)i
Subtraction: (a + bi) - (c + di) = (a - c) + (b - d)i
Multiplication: (a + bi) × (c + di) = (ac - bd) + (bc + ad)i
Division: (a + bi)/(c + di) = ((ac + bd) + (bc - ad)i)/(c2+ d2)
同时,所有数据类型都必须支持未知的值。你可能用自定义类型的一个状态来表示这个未知的值,也可以使用一个标志。当你创建一个自定义类型时,你可以实现System.Data.SqlTypes.INullable接口并创建一个共有静态只读的Null属性。
SQL Server能够识别类的继承,并且可以调用子类从基类继承的函数,但是SQL Server不能够直接地调用基类的函数。相反,在.NET应用程序中,基类的函数是可以被调用的,所以你希望在客户端能够调用一些额外的功能而服务器端不能使用的情况下,可以考虑使用类的继承。
你需要定义如何序列化你的自定义类型。形式分为Native与UserDefined。Native表示会将所有结构列出来。顺序表示先定义的变量会首先被列出来;正常化表示所有标量会被转换成字符串。
Native形式很方便,但是却有一个坏处,即你必须使用.NET的值类型来创建你的自定义类型。唯一有问题的是当你使用String类型时,由于String是一个引用,你必须使用自定义的序列化来处理它。同时,自定义类型需要支持XML。
C#以及VB的自定义类型可以在SQL Server 2005的范例中找到:C:\Program Files\Microsoft SQL Server\90\Samples\Engine\Programmability\CLR\UserDefinedDataType
更多的要求请见:http://msdn.microsoft.com/zh-cn/library/ms131082.aspx
第一部分是namespaces的声明:(引用中包含了正则表达式的namespaces。这个类型使用了Native形式,并且采用了INULLable接口)
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text.RegularExpressions;
using System.Globalization;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,IsByteOrdered = true)]
public struct ComplexNumberCS : INullable
{
第二部分是变量的声明:
//Regular expression used to parse values of the form (a,bi)
private static readonly Regex _parser = new Regex(@"\A\(\s*(?
// Real and imaginary parts
private double _real;
private double _imaginary;
// Internal member to show whether the value is null
private bool _isnull;
// Null value returned equal for all instances
private const string NULL = "<
private static readonly ComplexNumberCS NULL_INSTANCE = new ComplexNumberCS(true);
第三部分有两个构造函数:
// Constructor for a known value
public ComplexNumberCS(double real, double imaginary)
{
this._real = real;
this._imaginary = imaginary;
this._isnull = false;
}
// Constructor for an unknown value
private ComplexNumberCS(bool isnull)
{
this._isnull = isnull;
this._real = this._imaginary = 0;
}
第四部分是ToString函数:(正如之前提到的,ToString是每个自定义类型(UDT)必须具备的)
// Default string representation
public override string ToString()
{
return this._isnull ? NULL : ("(" + this._real.ToString(CultureInfo.InvariantCulture) + "," + this._imaginary.ToString(CultureInfo.InvariantCulture) + "i)");
}
第五部分是只读的函数,用于获取该类型的空值:
// Null handling
public bool IsNull {
get
{
return this._isnull;
}
}
public static ComplexNumberCS Null
{
get
{
return NULL_INSTANCE;
}
}
第六部分是Parse函数:
// Parsing input using regular expression
public static ComplexNumberCS Parse(SqlString sqlString)
{
string value = sqlString.ToString();
if (sqlString.IsNull || value == NULL)
return new ComplexNumberCS(true);
// Check whether the input value matches the regex pattern
Match m = _parser.Match(value);
// If the input's format is incorrect, throw an exception
if (!m.Success)
throw new ArgumentException( "Invalid format for complex number. " + "Format is (n, mi) where n and m are floating " + "point numbers in normal (not scientific) format " + "(nnnnnn.nn).");
// If everything is OK, parse the value;
// we will get two double type values
return new ComplexNumberCS(double.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture), double.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture));
}
第七部分实现了基本的运算:
// Properties to deal with real and imaginary parts separately
public double Real
{
get { if (this._isnull)
throw new InvalidOperationException();
return this._real;
}
set
{
this._real = value;
}
}
public double Imaginary
{
get
{
if (this._isnull)
throw new InvalidOperationException();
return this._imaginary;
}
set
{
this._imaginary = value;
}
}
第八部分定义了算术运算:
// Region with arithmetic operations
#region arithmetic operations
// Addition
public ComplexNumberCS AddCN(ComplexNumberCS c)
{
// null checking
if (this._isnull || c._isnull)
return new ComplexNumberCS(true);
// addition
return new ComplexNumberCS(this.Real + c.Real, this.Imaginary + c.Imaginary);
}
// Subtraction public ComplexNumberCS SubCN(ComplexNumberCS c)
{
// null checking
if (this._isnull || c._isnull)
return new ComplexNumberCS(true);
// subtraction
return new ComplexNumberCS(this.Real - c.Real, this.Imaginary - c.Imaginary);
}
// Multiplication
public ComplexNumberCS MulCN(ComplexNumberCS c)
{
// null checking
if (this._isnull || c._isnull)
return new ComplexNumberCS(true);
// multiplication
return new ComplexNumberCS(this.Real * c.Real - this.Imaginary * c.Imaginary, this.Imaginary * c.Real + this.Real * c.Imaginary);
}
// Division public ComplexNumberCS DivCN(ComplexNumberCS c)
{
// null checking
if (this._isnull || c._isnull)
return new ComplexNumberCS(true);
// division return new ComplexNumberCS( (this.Real * c.Real + this.Imaginary * c.Imaginary) / (c.Real * c.Real + c.Imaginary * c.Imaginary), (this.Imaginary * c.Real - this.Real * c.Imaginary) / (c.Real * c.Real + c.Imaginary * c.Imaginary) );
}
#endregion
}
sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO
接着利用T-SQL语言来导入Assembly:(PERMISSION_SET用来空值Assembly的权限)
CREATE ASSEMBLY ComplexNumberCS
FROM ' \ComplexNumberCS\ComplexNumberCS\bin\Debug\ComplexNumberCS.dll'
WITH PERMISSION_SET = SAFE;
现在你就可以在SQL Server中使用这个数据类型了,例如:
CREATE TABLE dbo.CNUsage
(
id INT IDENTITY(1,1) NOT NULL,
cn ComplexNumberCS NULL
);
但是需要注意的是,当你利用SELECT语句查询的时候,SQL Server并不能正确的翻译自定义类型。一个可能的查询结果如下:
ID | CN |
1 | 0xC000000000000000C00800000000000000 |
2 | 0xBFF0000000000000C01C00000000000000 |
SELECT id, cn.ToString() AS cn
FROM dbo. CNUsage;
ID | CN |
1 | (2,3i) |
2 | (1,7i) |
同时的,我们还可以利用C#来写出一个聚合函数,例如:
C# ComplexNumberCS_SUM UDA
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.Native)]
public struct ComplexNumberCS_SUM
{
ComplexNumberCS cn;
public void Init()
{
cn = ComplexNumberCS.Parse("(0, 0i)");
}
public void Accumulate(ComplexNumberCS Value)
{
cn = cn.AddCN(Value);
}
public void Merge(ComplexNumberCS_SUM Group)
{
Accumulate(Group.Terminate());
}
public ComplexNumberCS Terminate()
{
return cn;
}
}
接着可以将聚合函数加入SQL Server之中:
-- Alter assembly to add the ComplexNumberCS_SUM UDA
ALTER ASSEMBLY ComplexNumberCS
FROM 'C:\ComplexNumberCS\ComplexNumberCS\bin\Debug\ComplexNumberCS.dll';
GO
-- Create the aggregate function
CREATE AGGREGATE dbo.ComplexNumberCS_SUM(@input ComplexNumberCS)
RETURNS ComplexNumberCS
EXTERNAL NAME ComplexNumberCS.[ComplexNumberCS_SUM];
最后我们将可以在SQL Server中使用该聚合函数:
SELECT dbo.ComplexNumberCS_SUM(cn).ToString() AS ComplexSum
FROM CNUsage
WHERE cn IS NOT NULL;