ユーザー定義型の作成 - コーディング

適用対象:SQL Server

ユーザー定義型 (UDT) の定義をコーディングする際は、形式やシリアル化のオプションを選択するだけでなく、UDT をクラスと構造体のどちらで実装するかによって、さまざまな機能を実装する必要があります。

このセクションの例では、 Point UDT を 構造体 (または Visual Basic の 構造体 ) として実装する方法を示します。 Point 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 でコンパイルされた UDT などの Visual C++ データベース オブジェクトは、実行ではサポートされていません。

属性の指定

UDT のストレージ表現を構築し、クライアントに UDT を値として転送するためにシリアル化を使用する方法は、属性で決まります。

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute が必要です。 Serializable 属性は省略可能です。 また、Microsoft.SqlServer.Server.SqlFacetAttribute を指定して、UDT の戻り値の型に関する情報を提供することもできます。 詳細については、「CLR ルーチンのカスタム属性」を参照してください。

Point UDT の属性

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute、Point UDT のストレージ形式を Native に設定します。 IsByteOrderedtrue に設定すると、マネージド コードで同じ比較が行われた場合と同様に、SQL Serverで比較結果が同じであることが保証されます。 UDT は、UDT を null 対応にするために System.Data.SqlTypes.INullable インターフェイスを実装します。

次のコード フラグメントは、 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 インターフェイスを実装する必要があります。

値が CLR コード内から null かどうかを判断するために必要な IsNull という名前のプロパティを作成する必要があります。 SQL Serverが UDT の null インスタンスを検出すると、UDT は通常の null 処理メソッドを使用して永続化されます。 そのため、サーバーが NULL の UDT の不要なシリアル化やシリアル化解除に時間を費やしたり、NULL の UDT を格納して領域を無駄にすることはありません。 NULL に対するこのチェックは、UDT が CLR から引き継がれるたびに実行されます。つまり、Transact-SQL IS NULL コンストラクトを使用して null UDT をチェックすると、常に機能します。 IsNull プロパティは、インスタンスが null かどうかをテストするためにもサーバーによって使用されます。 UDT が NULL であることを判断できれば、サーバーはネイティブの NULL 処理を使用できます。

IsNullの get() メソッドは、特別な大文字と小文字は区別されません。 Point 変数@pNull の場合、@p.IsNull は既定で "1" ではなく "NULL" に評価されます。 これは、IsNull get() メソッドの SqlMethod(OnNullCall) 属性の既定値が false であるためです。 オブジェクトは Null であるため、プロパティが要求されると、オブジェクトは逆シリアル化されず、メソッドは呼び出されず、既定値 "NULL" が返されます。

次の例では、プライベート変数の is_Null に、UDT のインスタンスが NULL かどうかに関する状態が格納されます。 コードを作成する際は、is_Null の値を適切な状態に保つように注意する必要があります。 UDT には、UDT の null 値インスタンスを返す Null という名前の静的プロパティも必要です。 これにより、データベース内の UDT のインスタンスが実際に 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 返します。 クエリ 1 では、通常の Null 処理が使用されるため、UDT のシリアル化解除は必要ありません。 一方、クエリ 2 では、Null 以外の各オブジェクトを逆シリアル化し、CLR を呼び出して IsNull プロパティの値を取得する必要があります。 明らかに、 IS NULL を 使用するとパフォーマンスが向上し、Transact-SQL コードから UDT の IsNull プロパティを読み取る理由は決してありません。

では、 IsNull プロパティの使用方法は何ですか? まず、CLR コード内から値が Null かどうかを判断する必要があります。 2 つ目は、サーバーでインスタンスが Null かどうかをテストする方法が必要であるため、このプロパティはサーバーによって使用されます。 Null であると判断した後、ネイティブの null 処理を使用して処理できます。

Parse メソッドの実装

Parse メソッドと ToString メソッドを使用すると、UDT の文字列表現との間で変換を行えます。 Parse メソッドを使用すると、文字列を UDT に変換できます。 静的 (または Visual Basic では Shared) として宣言し、System.Data.SqlTypes.SqlString 型のパラメーターを受け取る必要があります。

次のコードは、X 座標と Y 座標を分離する Point UDT の Parse メソッドを実装しています。 Parse メソッドには System.Data.SqlTypes.SqlString 型の 1 つの引数があり、X 値と Y 値がコンマ区切り文字列として指定されていることを前提としています。 Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall 属性を false に設定すると、Point の null インスタンスから 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 を使用して Parse メソッドを反転させ、X 座標と Y 座標値で構成されるコンマ区切りの System.String を返します。 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 値を一定の範囲内に制限する必要があるときは、さらに検証が必要になります。 たとえば、日付を実装する UDT の日の値は、特定の有効範囲内の正の数である必要があります。

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttributeMicrosoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName プロパティを使用すると、データが UDT に割り当てられるか UDT に変換されるときにサーバーが実行する検証メソッドの名前を指定できます。 ValidationMethodName は、bcp ユーティリティ BULK INSERT、DBCC CHECKDB、DBCC CHECKFILEGROUP、DBCC CHECKTABLE、分散クエリ、および表形式データ ストリーム (TDS) リモート プロシージャ コール (RPC) 操作の実行中にも呼び出されます。 ValidationMethodName の既定値は null で、検証メソッドがないことを示します。

次のコード フラグメントは、ValidatePointValidationMethodName を指定する Point クラスの宣言を示しています。

<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 を 返すか、例外をスローした場合、値は無効として扱われ、エラーが発生します。

次のコード例では、0 以上の値のみが 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 による検証の例

ValidatePoint メソッドが Point クラスで確実に呼び出されるようにするには、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;  
}  

Property による検証の例

次のコード フラグメントは、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 で使用するメソッド用に独立したクラスを作成することを検討します。 アルゴリズムが変更された場合は、新しいコードでクラスを再コンパイルし、UDT に影響を与えることなくアセンブリをSQL Serverに読み込むことができます。 多くの場合、UDT は Transact-SQL ALTER ASSEMBLY ステートメントを使用して再読み込みできますが、既存のデータに問題が発生する可能性があります。 たとえば、AdventureWorks サンプル データベースに含まれる Currency UDT では、ConvertCurrency 関数を使用して通貨値を変換します。これは別のクラスに実装されています。 変換アルゴリズムが今後どう変化するかは予測できず、新しい機能が必要になる可能性もあります。 ConvertCurrency 関数を Currency UDT 実装から分離すると、将来の変更を計画する際の柔軟性が向上します。

Point クラスには、距離、DistanceFrom、DistanceFromXY の 3 つの簡単な方法が含まれています。 それぞれは、Point から 0 までの距離、指定したポイントから Point までの距離、および指定した X 座標と Y 座標から Point までの距離を計算する倍精度浮動小数点型 (double) の値を返します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 から FillRowMethodName フィールドと TableDefinition フィールドを継承します。 これは、一見テーブル値メソッドを記述できることを示していますが、この場合には該当しません。 メソッドがコンパイルされ、アセンブリが配置されますが、実行時に IEnumerable 戻り値の型に関するエラーが発生し、"アセンブリ 'assembly>' のクラス 'class>' のメソッド、プロパティ、またはフィールド '<<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 では、1 つの UPDATE ステートメントの SET 句で 2 つの UDT プロパティを設定することはできません。 ただし、2 つのメンバーを変更するミューテーターとしてメソッドをマークすることはできます。

注意

ミューテーター メソッドはクエリ内では使用できません。 代入ステートメントかデータ変更ステートメントでのみ、これらのメソッドを呼び出すことができます。 ミューテーターとしてマークされたメソッドが void を返さない (または Visual Basic の Sub ではない) 場合、CREATE TYPE はエラーで失敗します。

次のステートメントでは、Rotate メソッドを持つ Triangles UDT が存在することを前提としています。 次の Transact-SQL update ステートメントは 、Rotate メソッドを呼び出します。

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

Rotate メソッドは、メソッドをミューテーター メソッドとしてマークできるように、SqlMethod 属性の IsMutatortrue に設定SQL Server修飾されます。 また、 OnNullCallfalse に設定します。これは、入力パラメーターのいずれかが null 参照である場合に、メソッドが null 参照 (Visual Basic では Nothing ) を返したことをサーバーに示します。

<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 を実装する場合は、UDT データのシリアル化と逆シリアル化を処理するために、Microsoft.SqlServer.Server.IBinarySerialize インターフェイスを実装する Read メソッドと Write メソッドを実装する必要があります。 また、Microsoft.SqlServer.Server.SqlUserDefinedTypeAttributeMaxByteSize プロパティも指定する必要があります。

Currency UDT

Currency UDT は、SQL Server 2005 (9.x) 以降のSQL Serverでインストールできる CLR サンプルに含まれています。

Currency UDT は、特定のカルチャの通貨システムでの金額の処理をサポートしています。 2 つのフィールドを定義する必要があります。CultureInfo文字列は、通貨を発行したユーザー (例: en-us) と CurrencyValue10 進数 (金額) を指定します。

比較を実行するためにサーバーでは使用されませんが、 Currency UDT は System.IComparable インターフェイスを実装します。このインターフェイスは、単一のメソッド System.IComparable.CompareTo を公開します。 このインターフェイスは、クライアント側でカルチャ内の通貨値を正しく比較したり並べ替えたりする場合に使用されます。

CLR で実行されているコードでは、カルチャが通貨値とは別に比較されます。 Transact-SQL コードの場合、次のアクションによって比較が決定されます。

  1. IsByteOrdered 属性を true に設定すると、ディスク上の永続化されたバイナリ表現を比較に使用するようにSQL Serverに指示されます。

  2. 通貨 UDT の Write メソッドを使用して、ディスク上での UDT の永続化方法と、Transact-SQL 操作での UDT 値の比較と順序付け方法を決定します。

  3. 次のバイナリ形式を使用して Currency UDT を保存します。

    1. 0 バイト目から 19 バイト目までは UTF-16 エンコードされた文字列としてカルチャを保存します。右側の不足バイトには NULL 文字が埋め込まれます。

    2. 20 バイト目以降を使用して、通貨の 10 進値を保存します。

埋め込みの目的は、カルチャが通貨値から完全に分離されるようにすることです。そのため、Transact-SQL コードで 1 つの UDT を別の UDT と比較すると、カルチャ バイトがカルチャ バイトと比較され、通貨バイト値が通貨バイト値と比較されます。

Currency の属性

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 メソッドの作成

UserDefined シリアル化形式を選択する場合は、IBinarySerialize インターフェイスを実装し、独自の Read メソッドと Write メソッドを作成する必要もあります。 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型の作成