Crear tipos definidos por el usuario: codificación

Se aplica a:SQL Server

Al codificar la definición de un tipo definido por el usuario (UDT), debe implementar varias características, en función de si implementa el UDT como una clase o como una estructura, así como de las opciones de formato y serialización que haya elegido.

En el ejemplo de esta sección se muestra cómo implementar un UDT de punto como estructura (o Estructura en Visual Basic). El UDT point consta de coordenadas X e Y implementadas como procedimientos de propiedad.

Para definir un UDT, se requieren los espacios de nombres siguientes:

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

El espacio de nombres Microsoft.SqlServer.Server contiene los objetos necesarios para varios atributos de su UDT y el espacio de nombres System.Data.SqlTypes contiene las clases que representan SQL Server tipos de datos nativos disponibles para el ensamblado. Es obvio que puede haber espacios de nombres adicionales que el ensamblado necesite para funcionar correctamente. El UDT point también usa el espacio de nombres System.Text para trabajar con cadenas.

Nota:

No se admiten objetos de base de datos de Visual C++, como UDT, compilados con /clr:pure para su ejecución.

Especificar atributos

Los atributos determinan el modo de usar la serialización para construir la representación de almacenamiento de los UDT y para transmitirlos por valor al cliente.

Se requiere Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute . El atributo Serializable es opcional. También puede especificar Microsoft.SqlServer.Server.SqlFacetAttribute para proporcionar información sobre el tipo de valor devuelto de un UDT. Para obtener más información, vea Atributos personalizados para las rutinas CLR.

Atributos del UDT Point

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute establece el formato de almacenamiento del UDT de punto en nativo. IsByteOrdered se establece en true, lo que garantiza que los resultados de las comparaciones sean los mismos en SQL Server que si se hubiera realizado la misma comparación en código administrado. El UDT implementa la interfaz System.Data.SqlTypes.INullable para que el UDT sea compatible con null.

En el fragmento de código siguiente se muestran los atributos del UDT de punto .

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

Implementar la nulabilidad

Además de especificar correctamente los atributos de los ensamblados, el UDT también debe admitir la nulabilidad. Los UDT cargados en SQL Server son compatibles con valores NULL, pero para que el UDT reconozca un valor NULL, el UDT debe implementar la interfaz System.Data.SqlTypes.INullable.

Debe crear una propiedad denominada IsNull, que es necesaria para determinar si un valor es NULL desde el código CLR. Cuando SQL Server encuentra una instancia nula de un UDT, el UDT se conserva mediante métodos normales de control de valores NULL. El servidor no pierde tiempo serializando o deserializando el UDT sin necesidad y no desaprovecha espacio para almacenar un UDT NULL. Esta comprobación de valores NULL se realiza cada vez que se trae un UDT de CLR, lo que significa que el uso de la construcción NULL de IS de Transact-SQL para comprobar si hay UDT null siempre debe funcionar. El servidor también usa la propiedad IsNull para probar si una instancia es null. Una vez que el servidor determina que el UDT es NULL, puede usar su propio control de valores NULL nativos.

El método get() de IsNull no tiene mayúsculas y minúsculas especiales. Si una variable Point@p es Null, @p.IsNull se evaluará de forma predeterminada como "NULL", no "1". Esto se debe a que el atributo SqlMethod(OnNullCall) del método IsNull get() tiene como valor predeterminado false. Dado que el objeto es Null, cuando se solicita la propiedad, no se deserializa el objeto, no se llama al método y se devuelve un valor predeterminado de "NULL".

Ejemplo

En el ejemplo siguiente, la variable is_Null es privada y contiene el estado NULL para la instancia del UDT. El código debe mantener un valor adecuado para is_Null. El UDT también debe tener una propiedad estática denominada Null que devuelva una instancia de valor NULL del UDT. Esto permite al UDT devolver un valor NULL si la instancia también es NULL en la base de datos.

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 frente a IsNull

Considere una tabla que contenga el esquema Points(id int, location Point), donde Point es un UDT clR y las consultas siguientes:

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

Ambas consultas devuelven los identificadores de puntos con ubicaciones que no son NULL . En la consulta 1 (Query 1), se usa el control habitual de valores NULL y no se requiere ninguna deserialización de UDT. La consulta 2, por otro lado, tiene que deserializar cada objeto distinto de Null y llamar a CLR para obtener el valor de la propiedad IsNull . Claramente, el uso de IS NULL mostrará un mejor rendimiento y nunca debería haber una razón para leer la propiedad IsNull de un UDT desde código Transact-SQL.

¿Cuál es el uso de la propiedad IsNull ? En primer lugar, es necesario determinar si un valor es Null desde el código CLR. En segundo lugar, el servidor necesita una manera de probar si una instancia es Null, por lo que el servidor usa esta propiedad. Después de determinar que es Null, puede usar su control de valores NULL nativo para controlarlo.

Implementar el método Parse

Los métodos Parse y ToString permiten conversiones hacia y desde representaciones de cadena del UDT. El método Parse permite convertir una cadena en un UDT. Debe declararse como estático (o Compartido en Visual Basic) y tomar un parámetro de tipo System.Data.SqlTypes.SqlString.

El código siguiente implementa el método Parse para el UDT point , que separa las coordenadas X e Y. El método Parse tiene un único argumento de tipo System.Data.SqlTypes.SqlString y supone que los valores X e Y se proporcionan como una cadena delimitada por comas. Al establecer el atributo Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall enfalse , se impide que se llame al método Parse desde una instancia nula de 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;  
}  

Implementar el método ToString

El método ToString convierte el UDT point en un valor de cadena. En este caso, se devuelve la cadena "NULL" para una instancia Null del tipo Point . El método ToString invierte el método Parse mediante un system.Text.StringBuilder para devolver un system.string delimitado por comas que consta de los valores de coordenadas X e Y. Dado que InvokeIfReceiverIsNull tiene como valor predeterminado false, la comprobación de una instancia nula de Point no es necesaria.

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

Exponer las propiedades del UDT

El UDT point expone las coordenadas X e Y que se implementan como propiedades públicas de lectura y escritura del tipo 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;  
    }  
}  

Validar los valores UDT

Al trabajar con datos UDT, SQL Server motor de base de datos convierte automáticamente los valores binarios en valores UDT. Este proceso de conversión implica la comprobación de que los valores son adecuados para el formato de serialización del tipo, así como asegurarse de que el valor puede deserializarse correctamente. De este modo, se garantiza que el valor puede volver a convertirse a formato binario. En el caso de los UDT ordenados por bytes, esto también garantiza que el valor binario resultante coincida con el valor binario original. De esta forma, se evita que los valores que no son válidos se conserven en la base de datos. En algunos casos, este nivel de comprobación puede resultar inadecuado. Puede ser necesaria una validación adicional cuando se exige que los valores UDT se encuentren en un dominio o intervalo esperado. Por ejemplo, un UDT que implementa una fecha podría exigir que el valor de día sea un número positivo que pertenezca a un intervalo determinado de valores válidos.

La propiedad Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName de Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute permite proporcionar el nombre de un método de validación que el servidor ejecuta cuando los datos se asignan a un UDT o se convierten en un UDT. ValidationMethodName también se llama durante la ejecución de la utilidad bcp, BULK INSERT, DBCC CHECKDB, DBCC CHECKFILEGROUP, DBCC CHECKTABLE, consulta distribuida y operaciones de llamada a procedimiento remoto (RPC) del flujo de datos tabulares (TDS). El valor predeterminado de ValidationMethodName es null, lo que indica que no hay ningún método de validación.

Ejemplo

El siguiente fragmento de código muestra la declaración de la clase Point , que especifica un ValidationMethodName de 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  
{  

Si se especifica un método de validación, debe tener una firma de aspecto similar al fragmento de código siguiente.

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

El método de validación puede tener cualquier ámbito y debe devolver true si el valor es válido y false en caso contrario. Si el método devuelve false o produce una excepción, el valor se trata como no válido y se genera un error.

En el ejemplo siguiente, el código solamente permite valores iguales a cero o mayores que las coordenadas X e 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;  
    }  
}  

Limitaciones del método de validación

El servidor llama al método de validación cuando el servidor realiza conversiones, no cuando se insertan datos estableciendo propiedades individuales o cuando se insertan datos mediante una instrucción INSERT de Transact-SQL.

Debe llamar explícitamente al método de validación desde establecedores de propiedades y el método Parse si desea que el método de validación se ejecute en todas las situaciones. No se trata de ningún requisito e incluso, en algunos casos, es posible que no se desee.

Ejemplo de validación de Parse

Para asegurarse de que el método ValidatePoint se invoca en la clase Point , debe llamarlo desde el método Parse y desde los procedimientos de propiedad que establecen los valores de coordenadaS X e Y. El fragmento de código siguiente muestra cómo llamar al método de validación ValidatePoint desde la función 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;  
}  

Ejemplo de validación de propiedad

El fragmento de código siguiente muestra cómo llamar al método de validación ValidatePoint desde los procedimientos de propiedad que establecen las coordenadas X e 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.");  
        }  
    }  
}  

Codificar métodos UDT

Cuando codifique los métodos UDT, tenga en cuenta si el algoritmo usado podría cambiar con el tiempo. En ese caso, es posible que desee considerar la posibilidad de crear una clase independiente para los métodos que usa el UDT. Si cambia el algoritmo, puede volver a compilar la clase con el nuevo código y cargar el ensamblado en SQL Server sin afectar al UDT. En muchos casos, los UDT se pueden volver a cargar mediante la instrucción ALTER ASSEMBLY de Transact-SQL, pero esto podría causar problemas con los datos existentes. Por ejemplo, el UDT currency incluido con la base de datos de ejemplo AdventureWorks usa una función ConvertCurrency para convertir valores de moneda, que se implementa en una clase independiente. Es posible que en un futuro los algoritmos de conversión cambien de modo impredecible o que se necesite nueva funcionalidad. La separación de la función ConvertCurrencyde la implementación currency UDT proporciona una mayor flexibilidad al planear los cambios futuros.

Ejemplo

La clase Point contiene tres métodos simples para calcular la distancia: Distance, DistanceFrom y DistanceFromXY. Cada devuelve un cálculo doble de la distancia de Point a cero, la distancia desde un punto especificado a Point y la distancia desde las coordenadas X e Y especificadas a Point. Distance y DistanceFrom cada llamada a DistanceFromXY y muestran cómo usar argumentos diferentes para cada método.

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

Usar atributos SqlMethod

La clase Microsoft.SqlServer.Server.SqlMethodAttribute proporciona atributos personalizados que se pueden usar para marcar las definiciones de método con el fin de especificar el determinismo, el comportamiento de la llamada nula y especificar si un método es un mutador. Se asume el uso de valores predeterminados para estas propiedades y solamente se usa el atributo personalizado cuando se necesita un valor no predeterminado.

Nota:

La clase SqlMethodAttribute hereda de la clase SqlFunctionAttribute , por lo que SqlMethodAttribute hereda los campos FillRowMethodName y TableDefinition de SqlFunctionAttribute. Esto significa que es posible escribir un método con valores de tabla, que no es el caso. El método compila y el ensamblado se implementa, pero se genera un error sobre el tipo de valor devuelto IEnumerable en tiempo de ejecución con el siguiente mensaje: "Method, property o field 'name' in class' in assembly 'assembly' has invalid return type" (Método, propiedad o campo '<name>' en la clase '<class>' del ensamblado '<assembly>' tiene un tipo de valor devuelto no válido".

En la tabla siguiente se describen algunas de las propiedades Microsoft.SqlServer.Server.SqlMethodAttribute pertinentes que se pueden usar en los métodos UDT y se enumeran sus valores predeterminados.

DataAccess
Indica si la función implica acceso a los datos de usuario almacenados en la instancia local de SQL Server. El valor predeterminado es DataAccessKind. Ninguno.

IsDeterministic
Indica si la función genera los mismos valores de salida si se especifican los mismos valores de entrada y el mismo estado de la base de datos. El valor predeterminado es false.

IsMutator
Indica si el método produce un cambio de estado en la instancia del UDT. El valor predeterminado es false.

IsPrecise
Indica si la función implica cálculos imprecisos, como operaciones de punto flotante. El valor predeterminado es false.

OnNullCall
Indica si se llama al método cuando se especifican argumentos de entrada de referencia NULL. El valor predeterminado es true.

Ejemplo

La propiedad Microsoft.SqlServer.Server.SqlMethodAttribute.IsMutator permite marcar un método que permite un cambio en el estado de una instancia de un UDT. Transact-SQL no permite establecer dos propiedades UDT en la cláusula SET de una instrucción UPDATE. Sin embargo, puede tener un método marcado como mutador que cambie los dos miembros.

Nota:

No se permite el uso de métodos mutadores en las consultas. Solo puede llamarse a estos métodos en instrucciones de asignación o en instrucciones de modificación de datos. Si un método marcado como mutador no devuelve void (o no es un Sub en Visual Basic), CREATE TYPE produce un error.

En la siguiente instrucción se supone la existencia de un UDT de triángulos que tiene un método Rotate . La siguiente instrucción update de Transact-SQL invoca el método Rotate :

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

El método Rotate está decorado con el atributo SqlMethod estableciendo IsMutator en true para que SQL Server pueda marcar el método como un método mutador. El código también establece OnNullCall en false, que indica al servidor que el método devuelve una referencia nula (Nothing en Visual Basic) si alguno de los parámetros de entrada son referencias nulas.

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

Implementar un UDT con un formato definido por el usuario

Al implementar un UDT con un formato definido por el usuario, debe implementar métodos de lectura y escritura que implementen la interfaz Microsoft.SqlServer.Server.IBinarySerialize para controlar la serialización y deserialización de datos UDT. También debe especificar la propiedad MaxByteSize de Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.

El UDT Currency

El UDT currency se incluye con los ejemplos clR que se pueden instalar con SQL Server, empezando por SQL Server 2005 (9.x).

El UDT de moneda admite el manejo de cantidades de dinero en el sistema monetario de una referencia cultural determinada. Debe definir dos campos: una cadena para CultureInfo, que especifica quién emitió la moneda (por ejemplo, en-us) y un decimal para CurrencyValue, la cantidad de dinero.

Aunque el servidor no lo usa para realizar comparaciones, el UDT currency implementa la interfaz System.IComparable , que expone un único método, System.IComparable.CompareTo. Se usa del lado cliente en situaciones en las que se desean realizar comparaciones precisas u ordenar valores de moneda dentro de las referencias culturales.

El código que se ejecuta en CLR compara por separado la referencia cultural y el valor de moneda. En el caso del código transact-SQL, las siguientes acciones determinan la comparación:

  1. Establezca el atributo IsByteOrdered en true, que indica a SQL Server que use la representación binaria persistente en el disco para realizar comparaciones.

  2. Use el método Writepara currency UDT para determinar cómo se conserva el UDT en el disco y, por tanto, cómo se comparan y ordenan los valores UDT para las operaciones de Transact-SQL.

  3. Guarde el UDT de moneda con el siguiente formato binario:

    1. Guarde la referencia cultural como una cadena codificada mediante UTF-16, con bytes del 0 al 19, que se rellena a la derecha con caracteres NULL.

    2. Use el byte 20 y los bytes siguientes para almacenar el valor decimal de la moneda.

El propósito del relleno es asegurarse de que la referencia cultural está completamente separada del valor de moneda, de modo que, cuando un UDT se compara con otro en el código Transact-SQL, los bytes de referencia cultural se comparan con los bytes de referencia cultural y los valores de bytes de moneda se comparan con los valores de bytes de moneda.

Atributos de Currency

El UDT currency se define con los siguientes atributos.

<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  
    {  

Crear métodos de lectura y escritura con IBinarySerialize

Al elegir el formato de serialización UserDefined , también debe implementar la interfaz IBinarySerialize y crear sus propios métodos de lectura y escritura . Los procedimientos siguientes de currency UDT usan System.IO.BinaryReader y System.IO.BinaryWriter para leer y escribir en el 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();  
}  

Consulte también

Crear un tipo de User-Defined