Создание определяемых пользователем типов — программирование

Применимо к:SQL Server

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

В примере в этом разделе показано, как реализовать определяемый пользователем точечный тип в виде структуры (или структуры в Visual Basic). Определяемый пользователем тип точки состоит из координат 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 собственные типы данных, доступные для сборки. Разумеется, конкретной сборке для правильной работы могут понадобиться и другие пространства имен. Определяемый пользователем точечный тип также использует пространство имен System.Text для работы со строками.

Примечание

Объекты базы данных Visual C++, такие как определяемые пользователем типы, скомпилированные с помощью /clr:pure , не поддерживаются для выполнения.

Определение атрибутов

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

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

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

Атрибут Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute задает собственный формат хранилища для определяемого пользователем типаpoint. Для свойства 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, которое необходимо для определения того, является ли значение null в коде CLR. Когда SQL Server находит пустой экземпляр определяемого пользователем типа, определяемый пользователем тип сохраняется с помощью обычных методов обработки значений 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 ? Во-первых, это необходимо, чтобы определить, имеет ли значение NULL в коде CLR. Во-вторых, серверу требуется способ проверить, имеет ли экземпляр значение Null, поэтому это свойство используется сервером. Определив значение Null, он может использовать собственную обработку null для ее обработки.

Реализация синтаксического анализа

Методы Parse и ToString позволяют выполнять преобразования в строковые представления определяемого пользователем типа и из нее. Метод Parse позволяет преобразовать строку в определяемый пользователем тип. Он должен быть объявлен как статический (или общий в 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.

<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 возвращается для экземпляра Null типа Point . Метод ToString отменяет метод Parse , используя System.Text.StringBuilder для возврата разделенной запятыми строки System.String , состоящей из значений координат X и Y. Так как по умолчанию invokeIfReceiverIsNull имеет значение false, проверка для пустого экземпляра Point не требуется.

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

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

Определяемый пользователем тип точки предоставляет координаты 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) потока табличных данных (TDS). Значение по умолчанию для 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 , если требуется, чтобы метод проверки выполнялся во всех ситуациях. Это не является требованием и в некоторых случаях даже нежелательно.

Пример проверки при синтаксическом анализе

Чтобы убедиться, что метод 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, но это может привести к проблемам с существующими данными. Например, определяемый пользователем тип валюты , включенный в образец базы данных AdventureWorks , использует функцию ConvertCurrency для преобразования значений валют, которая реализуется в отдельном классе. Возможно, что в будущем алгоритмы преобразования изменятся непредсказуемым образом или потребуются новые функциональные возможности. Отделение функции ConvertCurrency от реализации определяемого пользователем типа currency обеспечивает большую гибкость при планировании будущих изменений.

Пример

Класс Point содержит три простых метода для вычисления расстояния: Distance, DistanceFrom и DistanceFromXY. Каждый возвращает двойное значение, вычисляющее расстояние от точки до нуля, расстояние от указанной точки до точки и расстояние от указанных координат X и Y до точки. Distance и DistanceF от каждого вызова 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 со следующим сообщением: "Метод, свойство или поле "<name>" в классе "<class>" в сборке "<assembly>" имеет недопустимый тип возвращаемого значения".

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

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

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

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

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

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

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

Определяемый пользователем тип currency входит в примеры среды CLR, которые можно установить с SQL Server, начиная с SQL Server 2005 (9.x).

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

Хотя он не используется сервером для сравнения, определяемый пользователем тип currency реализует интерфейс System.IComparable , который предоставляет один метод System.IComparable.CompareTo. Он используется на клиенте в ситуациях, когда необходимо провести точное сравнение или сортировку значений денежных сумм внутри культур.

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

  1. Присвойте атрибуту IsByteOrdered значение true, которое сообщает SQL Server использовать сохраненное двоичное представление на диске для сравнения.

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

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

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

    2. Байты с 20 и выше используются для сохранения десятичного значения денежной суммы.

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

Атрибуты 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 и создать собственные методы чтения и записи . В следующих процедурах из определяемого пользователем типа 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();  
}  

См. также:

Создание типа User-Defined