Поделиться через


Разработка кода для определяемых пользователем типов

При кодировании определяемого пользователем типа может потребоваться реализация разных функций в зависимости от того, внедряется ли определяемый пользователем тип как класс или как структура, а также от выбранных параметров формата и сериализации.

Пример в этом разделе иллюстрирует реализацию определяемого пользователем типа Point как типа struct (или Structure в Visual Basic). Определяемый пользователем тип Point содержит координаты X и Y, реализованные как процедуры свойства.

При определении определяемого пользователем типа требуются следующие пространства имен.

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

Пространство имен Microsoft.SqlServer.Server содержит объекты, необходимые для различных атрибутов определяемого пользователем типа, а пространство имен System.Data.SqlTypes содержит классы, соответствующие собственным типам данных SQL Server, доступным для сборки. Сборка может требовать и другие пространства имен для правильной работы. Определяемый пользователем тип Point также использует пространство имен System.Text для работы со строками.

ПримечаниеПримечание

Начиная с версии SQL Server 2005 выполнение объектов баз данных языка Visual C++ (например, определяемых пользователем типов), скомпилированных с параметром /clr:pure, не поддерживается.

Указание атрибутов

Атрибуты определяют, каким образом используется сериализация для создания хранимых представлений определяемых пользователем типов, а также для передачи таких типов клиенту по значению.

Атрибут Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute является обязательным. Атрибут Serializable является необязательным. Также можно указать атрибут Microsoft.SqlServer.Server.SqlFacetAttribute, чтобы предоставить информацию о возвращаемом типе определяемого пользователем типа. Дополнительные сведения см. в разделе Пользовательские атрибуты для процедур CLR.

Атрибуты определяемого пользователем типа Point

Атрибут Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute устанавливает для формата хранения определяемого пользователем типа Point значение Native. Для свойства IsByteOrdered устанавливается значение true, и это гарантирует, что результаты сравнения в SQL Server будут такими же, как если бы то же самое сравнение проводилось в управляемом коде. Определяемый пользователем тип реализует интерфейс System.Data.SqlTypes.INullable, чтобы определяемый пользователем тип мог работать со значениями NULL.

В следующем фрагменте кода показаны атрибуты определяемого пользователем типа Point.

<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

Помимо правильного указания атрибутов для сборок, определяемый пользователем тип должен также поддерживать значения NULL. Определяемые пользователем типы, загружаемые в SQL Server, умеют работать со значениями NULL, но чтобы определяемый пользователем тип распознавал значение NULL, в нем должен быть реализован интерфейс System.Data.SqlTypes.INullable.

Необходимо создать свойство с именем IsNull, необходимое, чтобы определять из кода CLR значение NULL. Если SQL Server обнаруживает экземпляр определяемого пользователем типа со значением NULL, он сохраняется с помощью обычных методов обработки значений NULL. Сервер не тратит время на сериализацию или десериализацию определяемого пользователем типа, если это не требуется, и не тратит место для хранения определяемого пользователем типа со значением NULL. Проверка значений NULL производится каждый раз при выводе определяемого пользователем типа из среды CLR, что означает, что использование конструкции Transact-SQL IS NULL для проверки значений NULL определяемых пользователем типов всегда работает. Сервер также использует свойство IsNull, чтобы проверять, имеет ли экземпляр значение NULL. Если сервер обнаруживает, что определяемый пользователем тип имеет значение NULL, он может использовать собственные средства обработки значений NULL.

Метод get() свойства IsNull не является особенным. Если в типе Point переменная @p имеет значение Null, то свойство @p.IsNull будет по умолчанию иметь значение NULL, а не 1. Это связано с тем, что атрибут SqlMethod(OnNullCall) метода IsNull get() по умолчанию имеет значение «false». Поскольку объект имеет значение Null, при запросе свойства не производится десериализация объекта, метод не вызывается и возвращается установленное по умолчанию значение NULL.

Пример

В следующем примере частная переменная is_Null хранит состояние NULL для экземпляра определяемого пользователем типа. В коде также должно поддерживаться соответствующее значение для переменной is_Null. Определяемый пользователем тип также должен содержать статическое свойство Null, возвращающее экземпляр определяемого пользователем типа со значением NULL. Это позволяет определяемому пользователем типу возвращать значение NULL, если экземпляр в базе данных также имеет значение 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, и следующие запросы:

--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. В запросе 1 используется обычная обработка значений NULL, и десериализация определяемых пользователем типов не требуется. В запросе 2 приходится выполнять десериализацию всех объектов, отличных от значения Null, и обращаться к среде CLR для получения значения свойства IsNull. Безусловно, использование конструкции IS NULL обеспечит более высокую производительность и избавит от необходимости считывания свойства IsNull для определяемого пользователем типа из кода Transact-SQL.

Итак, для чего нужно свойство IsNull? Во-первых, чтобы определять из кода CLR значение Null. Кроме того, серверу нужен способ проверять, имеет ли экземпляр значение Null, так что это свойство используется сервером. Если определяется значение Null, он может использовать собственные средства обработки значений NULL.

Реализация метода Parse

Методы Parse и ToString поддерживают преобразования определяемого пользователем типа в строковые представления и обратные преобразования. Метод Parse позволяет преобразовывать строку в определяемый пользователем тип. Он должен быть объявлен как static (или Shared в Visual Basic) и принимать параметр типа System.Data.SqlTypes.SqlString.

В следующем коде реализуется метод Parse для определяемого пользователем типа Point, разделяющий координаты X и Y. Метод Parse имеет один аргумент типа System.Data.SqlTypes.SqlString и предполагает, что значения X и Y предоставляются в виде строки, разделенной запятой. Установка для атрибута Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall значения false не позволяет вызвать метод Parse из экземпляра Point со значением NULL.

<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 в строковое значение. В этом случае строка «NULL» возвращается для пустого экземпляра типа Point. Метод ToString выполняет действие, обратное действию метода Parse, возвращая с помощью метода System.Text.StringBuilder значение типа System.String, состоящего из значений координат X и Y, разделенных запятыми. Поскольку свойство 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();
    }
}

Предоставление свойств определяемого пользователем типа

Определяемый пользователем тип Point предоставляет координаты X и Y, реализованные как открытые свойства типа System.Int32, доступные для чтения и записи.

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;
    }
}

Проверка значений определяемого пользователем типа

При работе с данными определяемого пользователем типа компонент SQL Server Database Engine автоматически преобразует двоичные значения в значения определяемого пользователем типа. Процесс преобразования включает проверку соответствия значений формату сериализации типа и возможности правильной десериализации. Это обеспечивает возможность обратного преобразования значения в двоичную форму. Для побайтно упорядоченных определяемых пользователем типов это также означает, что полученное при обратном преобразовании двоичное значение будет соответствовать исходному двоичному значению. Это позволяет предотвратить сохранение недопустимых значений в базе данных. В некоторых случаях такой уровень проверки может оказаться недостаточным. Дополнительная проверка требуется, если значения определяемого пользователем типа должны находиться в определенном домене или диапазоне. Например, если определяемый пользователем тип реализует дату, значение дня должно быть положительным числом, соответствующим определенному диапазону допустимых значений.

Свойство Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName типа Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute позволяет указать имя метода проверки, используемого сервером при присвоении данных определяемому пользователем типу или при их преобразовании в определяемый пользователем тип. Кроме того, свойство ValidationMethodName вызывается при запуске программы bcp, инструкций BULK INSERT, DBCC CHECKDB, DBCC CHECKFILEGROUP и DBCC CHECKTABLE, распределенных запросов, а также при операциях удаленного вызова процедур (RPC) потока табличных данных. По умолчанию свойство ValidationMethodName имеет значение NULL, т.е. метод проверки отсутствует.

Пример

В следующем фрагменте кода показана декларация класса Point, указывающая значение свойства ValidationMethodName для метода ValidatePoint.

<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

Чтобы убедиться, что метод ValidatePoint запускается из класса Point, необходимо вызвать его из метода Parse и из процедур свойства, устанавливающих значения координат X и Y. Следующий фрагмент кода показывает, как вызывать метод проверки ValidatePoint из функции 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))
    ' 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;
}

Пример проверки свойств

Следующий фрагмент кода показывает, как вызывать метод проверки ValidatePoint из процедур свойств, задающих координаты X и Y.

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.");
        }
    }
}

Программирование методов определяемого пользователем типа

При программировании методов определяемого пользователем типа необходимо учитывать, может ли используемый алгоритм меняться с течением времени. В этом случае можно создать отдельный класс для методов, используемых определяемым пользователем типом. При изменении алгоритма класс можно перекомпилировать с новым кодом и загрузить сборку в SQL Server без воздействия на определяемый пользователем тип. Во многих случаях определяемый пользователем тип можно перезагрузить с помощью инструкции Transact-SQL ALTER ASSEMBLY, но это может вызвать проблемы с существующими данными. Например, определяемый пользователем тип Currency из образца базы данных AdventureWorks использует для преобразования значений валюты функцию ConvertCurrency, реализованную в отдельном классе. Возможно в будущем алгоритмы преобразования будут изменяться непредсказуемым образом, а также могут потребоваться новые функции. Отделение функции ConvertCurrency от реализации определяемого пользователем типа Currency обеспечивает большую гибкость для планирования будущих изменений.

Сведения об установке образцов CLR AdventureWorks см. в разделе «Установка образцов» в электронной документации по SQL Server.

Пример

Класс Point содержит три простых метода расчета расстояния: Distance, DistanceFrom и DistanceFromXY. Каждый из методов возвращает тип double, содержащий рассчитанное расстояние от Point до нуля, расстояние от указанной точки до Point и расстояние от указанных координат X и Y до Point. Методы Distance и DistanceFrom вызывают метод 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, поэтому класс SqlMethodAttribute наследует поля FillRowMethodName и TableDefinition от класса SqlFunctionAttribute. Это подразумевает, что существует возможность написать возвращающий табличное значение метод, который не является вариантом. Этот метод компилируется, и сборка развертывается, однако, во время выполнения возникает ошибка возвращаемого типа интерфейса IEnumerable: «Метод, свойство или поле '<имя>' в классе '<класс>' в сборке '<сборка>' имеет недопустимый возвращаемый тип».

В следующей таблице описываются соответствующие свойства Microsoft.SqlServer.Server.SqlMethodAttribute, которые могут использоваться в методах определяемого пользователем типа, и приводятся их значения по умолчанию.

  • DataAccess
    Указывает, что функция производит доступ к пользовательским данным, хранящимся в локальном экземпляре SQL Server. Значение по умолчанию — DataAccessKind.None.

  • IsDeterministic
    Указывает, возвращает ли функция одинаковые выходные значения при передаче одинаковых входных значений и при одинаковом состоянии базы данных. Значение по умолчанию — false.

  • IsMutator
    Указывает, вызывает ли метод изменение состояния в экземпляре определяемого пользователем типа. Значение по умолчанию — false.

  • IsPrecise
    Указывает, содержит ли функция вычисления с потерей точности (например, операции с плавающей запятой). Значение по умолчанию — false.

  • OnNullCall
    Указывает, вызывается ли метод при указании значений NULL в качестве входных аргументов. Значение по умолчанию — true.

Пример

Свойство Microsoft.SqlServer.Server.SqlMethodAttribute.IsMutator позволяет отметить метод, позволяющий изменить состояния экземпляра определяемого пользователем типа. Transact-SQL не позволяет устанавливать два свойства определяемого пользователем типа в предложении SET одной инструкции UPDATE. Однако можно отметить метод как мутатор, меняющий два члена.

ПримечаниеПримечание

Методы, являющиеся мутаторами, нельзя использовать в запросах. Их можно вызывать только в инструкциях назначения или в инструкциях изменения данных. Если метод отмечен как мутатор, но не возвращает значение типа void (или не является Sub в Visual Basic), то инструкция CREATE TYPE завершается неудачно с ошибкой.

Следующая инструкция подразумевает существование определяемого пользователем типа Triangles, имеющего метод Rotate. Следующая инструкция обновления Transact-SQL вызывает метод Rotate:

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

В методе Rotate для атрибута SqlMethodIsMutator установлено значение true, так что SQL Server может отметить метод как мутатор. Код также устанавливает для параметра OnNullCall значение false, что указывает серверу, что метод возвращает значение NULL (Nothing в Visual Basic), если любой из входных параметров ссылается на значение 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);
}

Реализация определяемого пользователем типа в определяемом пользователем формате

При реализации определяемого пользователем типа в определяемом пользователем формате необходимо реализовать методы Read и Write, реализующие интерфейс Microsoft.SqlServer.Server.IBinarySerialize для сериализации и десериализации данных определяемого пользователем типа. Также необходимо указать свойство MaxByteSize атрибута Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.

Определяемый пользователем тип Currency

Определяемый пользователем тип Currency включен в образцы CLR, устанавливаемые с SQL Server, начиная с SQL Server 2005. Сведения об установке образцов CLR см. в разделе Вопросы установки образцов кода и образцов баз данных SQL Server.

Определяемый пользователем тип Currency поддерживает суммы в денежной системе определенной культуры. Необходимо определить два поля: типа string для поля CultureInfo, указывающий эмитента валюты (например, en-us) и типа decimal для поля CurrencyValue, указывающий сумму.

Хотя это не используется сервером для сравнения, определяемый пользователем тип Currency реализует интерфейс System.IComparable, применяющий один метод, System.IComparable.CompareTo. Он используется на клиенте, если нужно выполнить точное сравнение или сортировку значений валюты с учетом культуры.

Код CLR сравнивает культуры отдельно от значения валюты. Для кода Transact-SQL сравнение определяется следующими действиями.

  1. Установка для атрибута IsByteOrdered значения «true», в результате чего SQL Server использует для сравнения сохраненное двоичное представление на диске.

  2. Использование метода Write для определяемого пользователем типа Currency, чтобы определить способ сохранения определяемого пользователем типа на диске и, следовательно, способ сравнения и упорядочивания значений определяемого пользователем типа для операций Transact-SQL.

  3. Сохранение определяемого пользователем типа Currency в следующем двоичном формате.

    1. Сохранение культуры как строки в кодировке UTF-16 для байт 0-19, дополненной нулевыми символами до конца справа.

    2. Байты, начиная с 20, содержат десятичное значение валюты.

Цель заполнения заключается в том, чтобы полностью отделить культуру от значения валюты, чтобы при сравнении определяемого пользователем типа в коде Transact-SQL байты культуры сравнивались с байтами культуры, а байты валюты сравнивались с байтами валюты.

Чтобы подробно ознакомиться с листингом кода определяемого пользователем типа Currency, необходимо выполнить инструкции по установке образцов CLR в разделе Вопросы установки образцов кода и образцов баз данных SQL Server.

Атрибуты Currency

Определяемый пользователем тип Currency определяется со следующими атрибутами.

<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

При выборе формата сериализации UserDefined также необходимо реализовать интерфейс IBinarySerialize и создать собственные методы Read и Write. Следующие процедуры определяемого пользователем типа Currency используют методы System.IO.BinaryReader и System.IO.BinaryWriter для операций чтения и записи определяемого пользователем типа.

' 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();
}

Полный листинг кода определяемого пользователем типа Currency см. в разделе Вопросы установки образцов кода и образцов баз данных SQL Server.