创建用户定义类型 - 编码

适用于:SQL Server

编写用户定义类型 (UDT) 的定义时,必须根据是要将 UDT 作为类还是作为结构实现以及您所选择的格式和序列化选项来实现各种功能。

本节中的示例演示如何在 Visual Basic) 中将 Point UDT 实现为 结构 (或 结构 UDT 由作为属性过程实现的 X 和 Y 坐标组成。

定义 UDT 时需要下列命名空间:

Imports System  
Imports System.Data.SqlTypes  
Imports Microsoft.SqlServer.Server  
using System;  
using System.Data.SqlTypes;  
using Microsoft.SqlServer.Server;  

Microsoft.SqlServer.Server 命名空间包含 UDT 的各种属性所需的对象,System.Data.SqlTypes 命名空间包含表示程序集可用的SQL Server本机数据类型的类。 当然,还可能存在程序集正常运行所需的其他命名空间。 Point UDT 还使用 System.Text 命名空间处理字符串。

注意

不支持执行使用 /clr:pure 编译的 Visual C++ 数据库对象(例如 UDT)。

指定属性

属性确定如何使用序列化来构造 UDT 的存储表示形式以及如何按值将 UDT 传输到客户端。

需要 Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute可序列化属性是可选的。 还可以指定 Microsoft.SqlServer.Server.SqlFacetAttribute 来提供有关 UDT 返回类型的信息。 有关详细信息,请参阅 CLR 例程的自定义属性

Point UDT 属性

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute UDT 的存储格式设置为 NativeIsByteOrdered 设置为 true,这可以保证SQL Server中的比较结果与托管代码中发生的比较结果相同。 UDT 实现 System.Data.SqlTypes.INullable 接口,使 UDT 可识别 null。

以下代码片段显示了 Point UDT 的属性。

<Serializable(), SqlUserDefinedTypeAttribute(Format.Native, _  
  IsByteOrdered:=True)> _  
  Public Structure Point  
    Implements INullable  
[Serializable]  
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,  
  IsByteOrdered=true)]  
public struct Point : INullable  
{  

实现为 Null 性

除了为您的程序集正确指定属性外,UDT 还必须支持为 Null 性。 加载到SQL Server中的 UDT 可识别 null,但为了使 UDT 识别 null 值,UDT 必须实现 System.Data.SqlTypes.INullable 接口。

必须创建名为 IsNull 的属性,该属性是确定 CLR 代码中的值是否为 null 所必需的。 当SQL Server找到 UDT 的 null 实例时,将使用正常的 null 处理方法持久保存 UDT。 如果服务器不必序列化或反序列化 UDT,它不会在这些方面浪费时间;此外,它也不会浪费空间来存储 Null UDT。 每次从 CLR 引入 UDT 时,都会执行 null 的此检查,这意味着使用 Transact-SQL IS NULL 构造检查 null UDT 应始终正常工作。 服务器还使用 IsNull 属性来测试实例是否为 null。 一旦服务器确定 UDT 为 Null,它便可以使用其本机 Null 处理方法。

IsNullget () 方法没有任何特殊情况。 如果 Point 变量@pNull,则默认情况下,@p.IsNull 的计算结果为“NULL”,而不是“1”。 这是因为 IsNull get () 方法的 SqlMethod (OnNullCall) 属性默认为 false。 由于对象为 Null,因此在请求属性时不会反序列化对象,因此不会调用 方法,并返回默认值“NULL”。

示例

在下面的示例中,is_Null 变量是私有的并且保存了 UDT 实例的 Null 状态。 您的代码必须保留 is_Null 的相应值。 UDT 还必须具有一个名为 Null 的静态属性,该属性返回 UDT 的 null 值实例。 这样,如果该实例在数据库中确实为 Null,UDT 便可返回 Null 值。

Private is_Null As Boolean  
  
Public ReadOnly Property IsNull() As Boolean _  
   Implements INullable.IsNull  
    Get  
        Return (is_Null)  
    End Get  
End Property  
  
Public Shared ReadOnly Property Null() As Point  
    Get  
        Dim pt As New Point  
        pt.is_Null = True  
        Return (pt)  
    End Get  
End Property  
private bool is_Null;  
  
public bool IsNull  
{  
    get  
    {  
        return (is_Null);  
    }  
}  
  
public static Point Null  
{  
    get  
    {  
        Point pt = new Point();  
        pt.is_Null = true;  
        return pt;  
    }  
}  

IS NULL 与 IsNull 的比较

考虑一个表,其中包含架构 Points (id int、location Point) ,其中 Point 是 CLR UDT,以及以下查询:

--Query 1  
SELECT ID  
FROM Points  
WHERE NOT (location IS NULL) -- Or, WHERE location IS NOT NULL;  
--Query 2:  
SELECT ID  
FROM Points  
WHERE location.IsNull = 0;  

这两个查询都返回具有非 Null 位置的点的 ID。 在 Query 1 中,使用的是正常 Null 处理方法,不需要反序列化 UDT。 另一方面,查询 2 必须反序列化每个非 Null 对象,并调用 CLR 以获取 IsNull 属性的值。 显然,使用 IS NULL 将表现出更好的性能,并且永远没有理由从 Transact-SQL 代码读取 UDT 的 IsNull 属性。

那么, IsNull 属性的用途是什么? 首先,需要确定 CLR 代码中的值是否为 Null 。 其次,服务器需要一种方法来测试实例是否为 Null,因此服务器使用此属性。 确定为 Null 后,可以使用其本机 null 处理来处理它。

实现 Parse 方法

ParseToString 方法允许与 UDT 的字符串表示形式进行转换。 Parse 方法允许将字符串转换为 UDT。 它必须在 Visual Basic) 中声明为 静态 (或 共享 ,并采用 类型为 System.Data.SqlTypes.SqlString 的参数

以下代码实现 Point UDT 的 Parse 方法,该方法分离出 X 和 Y 坐标。 Parse 方法具有一个类型为 System.Data.SqlTypes.SqlString 的参数,并假定 X 和 Y 值以逗号分隔的字符串形式提供。 将 Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall 属性设置为 false 可防止从 Point 的空实例调用 Parse 方法。

<SqlMethod(OnNullCall:=False)> _  
Public Shared Function Parse(ByVal s As SqlString) As Point  
    If s.IsNull Then  
        Return Null  
    End If  
  
    ' Parse input string here to separate out points.  
    Dim pt As New Point()  
    Dim xy() As String = s.Value.Split(",".ToCharArray())  
    pt.X = Int32.Parse(xy(0))  
    pt.Y = Int32.Parse(xy(1))  
    Return pt  
End Function  
[SqlMethod(OnNullCall = false)]  
public static Point Parse(SqlString s)  
{  
    if (s.IsNull)  
        return Null;  
  
    // Parse input string to separate out points.  
    Point pt = new Point();  
    string[] xy = s.Value.Split(",".ToCharArray());  
    pt.X = Int32.Parse(xy[0]);  
    pt.Y = Int32.Parse(xy[1]);  
    return pt;  
}  

实现 ToString 方法

ToString 方法将 Point UDT 转换为字符串值。 在这种情况下,为 Point 类型的 Null 实例返回字符串“NULL”。 ToString 方法使用 System.Text.StringBuilder 返回由 X 和 Y 坐标值组成的逗号分隔的 System.String,从而反转 Parse 方法。 由于 InvokeIfReceiverIsNull 默认为 false,因此不需要为 Point 的 null 实例检查。

Private _x As Int32  
Private _y As Int32  
  
Public Overrides Function ToString() As String  
    If Me.IsNull Then  
        Return "NULL"  
    Else  
        Dim builder As StringBuilder = New StringBuilder  
        builder.Append(_x)  
        builder.Append(",")  
        builder.Append(_y)  
        Return builder.ToString  
    End If  
End Function  
private Int32 _x;  
private Int32 _y;  
  
public override string ToString()  
{  
    if (this.IsNull)  
        return "NULL";  
    else  
    {  
        StringBuilder builder = new StringBuilder();  
        builder.Append(_x);  
        builder.Append(",");  
        builder.Append(_y);  
        return builder.ToString();  
    }  
}  

公开 UDT 属性

Point UDT 公开作为 System.Int32 类型的公共读写属性实现的 X 和 Y 坐标。

Public Property X() As Int32  
    Get  
        Return (Me._x)  
    End Get  
  
    Set(ByVal Value As Int32)  
        _x = Value  
    End Set  
End Property  
  
Public Property Y() As Int32  
    Get  
        Return (Me._y)  
    End Get  
  
    Set(ByVal Value As Int32)  
        _y = Value  
    End Set  
End Property  
public Int32 X  
{  
    get  
    {  
        return this._x;  
    }  
    set   
    {  
        _x = value;  
    }  
}  
  
public Int32 Y  
{  
    get  
    {  
        return this._y;  
    }  
    set  
    {  
        _y = value;  
    }  
}  

验证 UDT 值

处理 UDT 数据时,SQL Server数据库引擎会自动将二进制值转换为 UDT 值。 该转换过程包括检查值是否符合类型的序列化格式和确保可以正确地反序列化值。 这样就确保了值可以转换回二进制形式。 对于采用字节顺序的 UDT,这样还可以确保产生的二进制值与原始二进制值匹配。 这会防止在数据库中保留无效值。 在某些情况下,只进行这种级别的检查可能是不够的。 当要求 UDT 值在预期的域或范围之内时可能需要进行其他验证。 例如,实现日期的 UDT 可能要求日值为特定的有效值范围内的正数。

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttributeMicrosoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName 属性允许提供在将数据分配给 UDT 或转换为 UDT 时服务器运行的验证方法的名称。 在运行 bcp 实用工具、BULK INSERT、DBCC CHECKDB、DBCC CHECKFILEGROUP、DBCC CHECKTABLE、分布式查询和表格数据流 (TDS) RPC) 操作的远程过程 (调用期间,也会调用 ValidationMethodNameValidationMethodName 的默认值为 null,表示没有验证方法。

示例

以下代码片段显示了 Point 类的声明,该声明指定 ValidatePointValidationMethodName

<Serializable(), SqlUserDefinedTypeAttribute(Format.Native, _  
  IsByteOrdered:=True, _  
  ValidationMethodName:="ValidatePoint")> _  
  Public Structure Point  
[Serializable]  
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,  
  IsByteOrdered=true,   
  ValidationMethodName = "ValidatePoint")]  
public struct Point : INullable  
{  

如果指定了验证方法,则必须具有如下面的代码段所示的签名。

Private Function ValidationFunction() As Boolean  
    If (validation logic here) Then  
        Return True  
    Else  
        Return False  
    End If  
End Function  
private bool ValidationFunction()  
{  
    if (validation logic here)  
    {  
        return true;  
    }  
    else  
    {  
        return false;  
    }  
}  

验证方法可以具有任何范围,如果值有效,应返回 true ,否则返回 false 。 如果方法返回 false 或引发异常,则将该值视为无效并引发错误。

在下面的示例中,代码仅允许零值或大于 X 和 Y 坐标的值。

Private Function ValidatePoint() As Boolean  
    If (_x >= 0) And (_y >= 0) Then  
        Return True  
    Else  
        Return False  
    End If  
End Function  
private bool ValidatePoint()  
{  
    if ((_x >= 0) && (_y >= 0))  
    {  
        return true;  
    }  
    else  
    {  
        return false;  
    }  
}  

验证方法限制

服务器在执行转换时调用验证方法,而不是通过设置单个属性插入数据或使用 Transact-SQL INSERT 语句插入数据时调用验证方法。

如果希望验证方法在所有情况下执行,则必须从属性资源库和 Parse 方法显式调用验证方法。 并不要求这样做,在某些情况下可能甚至不需要这样做。

Parse 验证示例

若要确保在 Point 类中调用 ValidatePoint 方法,必须从 Parse 方法和设置 X 和 Y 坐标值的属性过程中调用该方法。 以下代码片段演示如何从 Parse 函数调用 ValidatePoint 验证方法。

<SqlMethod(OnNullCall:=False)> _  
Public Shared Function Parse(ByVal s As SqlString) As Point  
    If s.IsNull Then  
        Return Null  
    End If  
  
    ' Parse input string here to separate out points.  
    Dim pt As New Point()  
    Dim xy() As String = s.Value.Split(",".ToCharArray())  
    pt.X = Int32.Parse(xy(0))  
    pt.Y = Int32.Parse(xy(1))  
  
    ' Call ValidatePoint to enforce validation  
    ' for string conversions.  
    If Not pt.ValidatePoint() Then  
        Throw New ArgumentException("Invalid XY coordinate values.")  
    End If  
    Return pt  
End Function  
[SqlMethod(OnNullCall = false)]  
public static Point Parse(SqlString s)  
{  
    if (s.IsNull)  
        return Null;  
  
    // Parse input string to separate out points.  
    Point pt = new Point();  
    string[] xy = s.Value.Split(",".ToCharArray());  
    pt.X = Int32.Parse(xy[0]);  
    pt.Y = Int32.Parse(xy[1]);  
  
    // Call ValidatePoint to enforce validation  
    // for string conversions.  
    if (!pt.ValidatePoint())   
        throw new ArgumentException("Invalid XY coordinate values.");  
    return pt;  
}  

属性验证示例

以下代码片段演示如何从设置 X 和 Y 坐标的属性过程中调用 ValidatePoint 验证方法。

Public Property X() As Int32  
    Get  
        Return (Me._x)  
    End Get  
  
    Set(ByVal Value As Int32)  
        Dim temp As Int32 = _x  
        _x = Value  
        If Not ValidatePoint() Then  
            _x = temp  
            Throw New ArgumentException("Invalid X coordinate value.")  
        End If  
    End Set  
End Property  
  
Public Property Y() As Int32  
    Get  
        Return (Me._y)  
    End Get  
  
    Set(ByVal Value As Int32)  
        Dim temp As Int32 = _y  
        _y = Value  
        If Not ValidatePoint() Then  
            _y = temp  
            Throw New ArgumentException("Invalid Y coordinate value.")  
        End If  
    End Set  
End Property  
    public Int32 X  
{  
    get  
    {  
        return this._x;  
    }  
    // Call ValidatePoint to ensure valid range of Point values.  
    set   
    {  
        Int32 temp = _x;  
        _x = value;  
        if (!ValidatePoint())  
        {  
            _x = temp;  
            throw new ArgumentException("Invalid X coordinate value.");  
        }  
    }  
}  
  
public Int32 Y  
{  
    get  
    {  
        return this._y;  
    }  
    set  
    {  
        Int32 temp = _y;  
        _y = value;  
        if (!ValidatePoint())  
        {  
            _y = temp;  
            throw new ArgumentException("Invalid Y coordinate value.");  
        }  
    }  
}  

UDT 方法编码

编写 UDT 方法时,请考虑所用的算法是否可能会随时间的推移而改变。 如果可能改变的话,最好考虑为您的 UDT 所用的方法创建一个单独的类。 如果算法发生更改,可以使用新代码重新编译类,并将程序集加载到SQL Server中,而不会影响 UDT。 在许多情况下,可以使用 Transact-SQL ALTER ASSEMBLY 语句重新加载 UDT,但这可能会导致现有数据出现问题。 例如,AdventureWorks 示例数据库附带的 Currency UDT 使用 ConvertCurrency 函数转换货币值,该函数在单独的类中实现。 在未来,转换算法可能会发生不可预知的变化,或者可能需要新的功能。 在规划未来更改时,将 ConvertCurrency 函数与 货币 UDT 实现分离可提供更大的灵活性。

示例

Point 类包含三种用于计算距离的简单方法:DistanceDistanceFromDistanceFromXY。 每个返回一个 双精度 值,计算从 到零的距离、从指定点到 的距离以及从指定的 X 和 Y 坐标到 的距离。 DistanceDistanceFrom 分别调用 DistanceFromXY,并演示如何对每个方法使用不同的参数。

' Distance from 0 to Point.  
<SqlMethod(OnNullCall:=False)> _  
Public Function Distance() As Double  
    Return DistanceFromXY(0, 0)  
End Function  
  
' Distance from Point to the specified point.  
<SqlMethod(OnNullCall:=False)> _  
Public Function DistanceFrom(ByVal pFrom As Point) As Double  
    Return DistanceFromXY(pFrom.X, pFrom.Y)  
End Function  
  
' Distance from Point to the specified x and y values.  
<SqlMethod(OnNullCall:=False)> _  
Public Function DistanceFromXY(ByVal ix As Int32, ByVal iy As Int32) _  
    As Double  
    Return Math.Sqrt(Math.Pow(ix - _x, 2.0) + Math.Pow(iy - _y, 2.0))  
End Function  
// Distance from 0 to Point.  
[SqlMethod(OnNullCall = false)]  
public Double Distance()  
{  
    return DistanceFromXY(0, 0);  
}  
  
// Distance from Point to the specified point.  
[SqlMethod(OnNullCall = false)]  
public Double DistanceFrom(Point pFrom)  
{  
    return DistanceFromXY(pFrom.X, pFrom.Y);  
}  
  
// Distance from Point to the specified x and y values.  
[SqlMethod(OnNullCall = false)]  
public Double DistanceFromXY(Int32 iX, Int32 iY)  
{  
    return Math.Sqrt(Math.Pow(iX - _x, 2.0) + Math.Pow(iY - _y, 2.0));  
}  

使用 SqlMethod 属性

Microsoft.SqlServer.Server.SqlMethodAttribute 类提供自定义属性,可用于标记方法定义以指定确定性、在 null 调用行为上,以及指定方法是否为可变器。 使用的是这些属性的默认值,仅在需要非默认值时才使用自定义特性。

注意

SqlMethodAttribute 类继承自 SqlFunctionAttribute 类,因此 SqlMethodAttributeSqlFunctionAttribute 继承 FillRowMethodNameTableDefinition 字段。 这意味着可以编写一个不属于此情况的表值方法。 方法编译并部署程序集,但在运行时引发有关 IEnumerable 返回类型的错误,并显示以下消息:“程序集'assembly>>'类中的方法、属性或字段'<<name>'<具有无效的返回类型。”

下表描述了可在 UDT 方法中使用的一些相关 Microsoft.SqlServer.Server.SqlMethodAttribute 属性,并列出了其默认值。

DataAccess
指示函数是否需要访问存储在 SQL Server 的本地实例中的用户数据。 默认值为 DataAccessKind

IsDeterministic
指示在输入值相同且数据库状态相同的情况下函数是否会生成相同的输出值。 默认值为 false

IsMutator
指示方法是否在 UDT 实例中引起状态变化。 默认值为 false

IsPrecise
指示函数是否涉及不精确的计算,如浮点运算。 默认值为 false

OnNullCall
指示在指定 Null 引用输入参数时是否调用此方法。 默认值为 true。

示例

Microsoft.SqlServer.Server.SqlMethodAttribute.IsMutator 属性允许标记允许更改 UDT 实例状态的方法。 Transact-SQL 不允许在一个 UPDATE 语句的 SET 子句中设置两个 UDT 属性。 然而,您可以将一个方法标记为更改两个成员的赋值函数。

注意

在查询中不允许使用赋值函数方法。 这些方法只能在赋值语句或数据修改语句中调用。 如果标记为 mutator 的方法不返回 void (或不是 Visual Basic) 中的 Sub ,则 CREATE TYPE 将失败并显示错误。

以下语句假定存在具有 Rotate 方法的三角形 UDT。 以下 Transact-SQL 更新语句调用 Rotate 方法:

UPDATE Triangles SET t.RotateY(0.6) WHERE id=5  

Rotate 方法使用 SqlMethod 属性将 IsMutator 设置为 true 进行修饰,以便SQL Server可以将该方法标记为 mutator 方法。 代码还将 OnNullCall 设置为 false,这向服务器指示该方法返回 null 引用 (Visual Basic) 如果任何输入参数为 null 引用,则该方法返回 null 引用。

<SqlMethod(IsMutator:=True, OnNullCall:=False)> _  
Public Sub Rotate(ByVal anglex as Double, _  
  ByVal angley as Double, ByVal anglez As Double)   
   RotateX(anglex)  
   RotateY(angley)  
   RotateZ(anglez)  
End Sub  
[SqlMethod(IsMutator = true, OnNullCall = false)]  
public void Rotate(double anglex, double angley, double anglez)   
{  
   RotateX(anglex);  
   RotateY(angley);  
   RotateZ(anglez);  
}  

采用用户定义的格式实现 UDT

使用用户定义的格式实现 UDT 时,必须实现实现 Microsoft.SqlServer.Server.IBinarySerialize 接口的 读取写入 方法,以处理 UDT 数据的序列化和反序列化。 还必须指定 Microsoft.SqlServer.Server.SqlUserDefinedTypeAttributeMaxByteSize 属性。

Currency UDT

货币 UDT 包含在可与 SQL Server 一起安装的 CLR 示例,从 2005 SQL Server 2005 (9.x) 开始。

货币 UDT 支持处理特定文化货币系统中的货币金额。 必须定义两个字段:CultureInfo字符串,用于指定 (en-us 发行货币的人员,例如) 和 CurrencyValue(货币金额)的十进制数。

尽管服务器不使用它来执行比较, 但 Currency UDT 实现了 System.IComparable 接口,该接口公开了单个方法 System.IComparable.CompareTo。 在需要精确比较或排序区域性中的货币值的情况下,在客户端会使用此方法。

在 CLR 中运行的代码将区域性与货币值分开比较。 对于 Transact-SQL 代码,以下操作确定比较:

  1. IsByteOrdered 属性设置为 true,告知SQL Server使用磁盘上的持久化二进制表示形式进行比较。

  2. 使用 Currency UDT 的 Write 方法可确定 UDT 如何在磁盘上保留,从而确定如何比较和排序 Transact-SQL 操作的 UDT 值。

  3. 使用以下二进制格式保存 货币 UDT:

    1. 将区域性另存为 UTF-16 编码的字符串并用第 0-19 个字节表示,右侧以 Null 字符填充。

    2. 使用第 20 个字节及以后的字节包含货币的十进制值。

填充的目的是确保区域性与货币值完全分离,以便在 Transact-SQL 代码中将一个 UDT 与另一个 UDT 进行比较时,区域性字节与区域性字节进行比较,货币字节值与货币字节值进行比较。

Currency 属性

货币 UDT 使用以下属性定义。

<Serializable(), Microsoft.SqlServer.Server.SqlUserDefinedType( _  
    Microsoft.SqlServer.Server.Format.UserDefined, _  
    IsByteOrdered:=True, MaxByteSize:=32), _  
    CLSCompliant(False)> _  
Public Structure Currency  
Implements INullable, IComparable, _  
Microsoft.SqlServer.Server.IBinarySerialize  
[Serializable]  
[SqlUserDefinedType(Format.UserDefined,   
    IsByteOrdered = true, MaxByteSize = 32)]  
    [CLSCompliant(false)]  
    public struct Currency : INullable, IComparable, IBinarySerialize  
    {  

创建带有 IBinarySerialize 的 Read 和 Write 方法

选择 “用户定义的 序列化格式”时,还必须实现 IBinarySerialize 接口并创建自己的 读取写入 方法。 Currency UDT 中的以下过程使用 System.IO.BinaryReaderSystem.IO.BinaryWriter 从 UDT 读取和写入。

' IBinarySerialize methods  
' The binary layout is as follow:  
'    Bytes 0 - 19: Culture name, padded to the right with null  
'    characters, UTF-16 encoded  
'    Bytes 20+: Decimal value of money  
' If the culture name is empty, the currency is null.  
Public Sub Write(ByVal w As System.IO.BinaryWriter) _  
  Implements Microsoft.SqlServer.Server.IBinarySerialize.Write  
    If Me.IsNull Then  
        w.Write(nullMarker)  
        w.Write(System.Convert.ToDecimal(0))  
        Return  
    End If  
  
    If cultureName.Length > cultureNameMaxSize Then  
        Throw New ApplicationException(String.Format(CultureInfo.CurrentUICulture, _  
           "{0} is an invalid culture name for currency as it is too long.", cultureNameMaxSize))  
    End If  
  
    Dim paddedName As String = cultureName.PadRight(cultureNameMaxSize, CChar(vbNullChar))  
  
    For i As Integer = 0 To cultureNameMaxSize - 1  
        w.Write(paddedName(i))  
    Next i  
  
    ' Normalize decimal value to two places  
    currencyVal = Decimal.Floor(currencyVal * 100) / 100  
    w.Write(currencyVal)  
End Sub  
  
Public Sub Read(ByVal r As System.IO.BinaryReader) _  
  Implements Microsoft.SqlServer.Server.IBinarySerialize.Read  
    Dim name As Char() = r.ReadChars(cultureNameMaxSize)  
    Dim stringEnd As Integer = Array.IndexOf(name, CChar(vbNullChar))  
  
    If stringEnd = 0 Then  
        cultureName = Nothing  
        Return  
    End If  
  
    cultureName = New String(name, 0, stringEnd)  
    currencyVal = r.ReadDecimal()  
End Sub  
  
// IBinarySerialize methods  
// The binary layout is as follow:  
//    Bytes 0 - 19:Culture name, padded to the right   
//    with null characters, UTF-16 encoded  
//    Bytes 20+:Decimal value of money  
// If the culture name is empty, the currency is null.  
public void Write(System.IO.BinaryWriter w)  
{  
    if (this.IsNull)  
    {  
        w.Write(nullMarker);  
        w.Write((decimal)0);  
        return;  
    }  
  
    if (cultureName.Length > cultureNameMaxSize)  
    {  
        throw new ApplicationException(string.Format(  
            CultureInfo.InvariantCulture,   
            "{0} is an invalid culture name for currency as it is too long.",   
            cultureNameMaxSize));  
    }  
  
    String paddedName = cultureName.PadRight(cultureNameMaxSize, '\0');  
    for (int i = 0; i < cultureNameMaxSize; i++)  
    {  
        w.Write(paddedName[i]);  
    }  
  
    // Normalize decimal value to two places  
    currencyValue = Decimal.Floor(currencyValue * 100) / 100;  
    w.Write(currencyValue);  
}  
public void Read(System.IO.BinaryReader r)  
{  
    char[] name = r.ReadChars(cultureNameMaxSize);  
    int stringEnd = Array.IndexOf(name, '\0');  
  
    if (stringEnd == 0)  
    {  
        cultureName = null;  
        return;  
    }  
  
    cultureName = new String(name, 0, stringEnd);  
    currencyValue = r.ReadDecimal();  
}  

另请参阅

创建User-Defined类型