Codage de types définis par l'utilisateur

Lorsque vous codez votre définition de type défini par l'utilisateur (UDT, User-Defined Type), vous devez implémenter différentes fonctionnalités, selon que vous implémentez le type défini par l'utilisateur comme classe ou comme structure, et selon les options de format et de sérialisation que vous avez choisies.

L'exemple de cette section illustre l'implémentation d'un type défini par l'utilisateur Point en tant que struct (ou Structure dans Visual Basic). Le type défini par l'utilisateur Point consiste en coordonnées X et Y implémentées comme procédures de propriété.

Les espaces de noms suivants sont requis lors de la définition d'un type défini par l'utilisateur :

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

L'espace de noms Microsoft.SqlServer.Server contient les objets requis pour différents attributs de votre type défini par l'utilisateur et l'espace de noms System.Data.SqlTypes contient les classes qui représentent les types de données natifs SQL Server disponibles à l'assembly. Il se peut bien sûr que le bon fonctionnement de votre assembly requiert des espaces de noms supplémentaires. Le type défini par l'utilisateur Point utilise également l'espace de noms System.Text pour manipuler des chaînes.

Notes

À compter de SQL Server 2005, les objets de base de données Visual C++, tels que les types définis par l'utilisateur, compilés avec /clr:pure ne sont pas pris en charge pour l'exécution.

Spécification d'attributs

Les attributs déterminent la façon dont la sérialisation est utilisée pour construire la représentation de stockage des types définis par l'utilisateur et pour transmettre des types définis par l'utilisateur par valeur au client.

L'attribut Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute est requis. L'attribut Serializable est facultatif. Vous pouvez également spécifier l'attribut Microsoft.SqlServer.Server.SqlFacetAttribute pour fournir des informations à propos du type de retour d'un type défini par l'utilisateur. Pour plus d'informations, consultez Attributs personnalisés pour les routines CLR.

Attributs du type défini par l'utilisateur Point

L’attribut Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute définit le format de stockage pour le type défini par l'utilisateur Point à Native. IsByteOrdered a la valeur true, ce qui garantit que les résultats des comparaisons dans SQL Server sont les mêmes que si les comparaisons avaient eu lieu dans le code managé. Le type défini par l'utilisateur implémente l'interface System.Data.SqlTypes.INullable pour rendre le type défini par l'utilisateur compatible avec la valeur NULL.

Le fragment de code suivant montre les attributs pour le type défini par l'utilisateur 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
{

Implémentation de la possibilité de valeur Null

En plus de spécifier correctement les attributs pour vos assemblys, votre type défini par l'utilisateur doit également prendre en charge la possibilité de valeur Null. Les types définis par l'utilisateur chargés dans SQL Server sont compatibles avec la valeur Null, mais pour reconnaître une valeur Null, le type défini par l'utilisateur doit implémenter l'interface System.Data.SqlTypes.INullable.

Vous devez créer une propriété nommée IsNull, nécessaire pour déterminer si une valeur est Null dans le code CLR. Lorsque SQL Server trouve une instance Null d'un type défini par l'utilisateur, celui-ci est rendu persistant à l'aide de méthodes de gestion de valeur Null ordinaires. Le serveur ne perd pas de temps à sérialiser ou désérialiser le type défini par l'utilisateur si cela n'est pas nécessaire et il ne gaspille pas d'espace pour stocker un type défini par l'utilisateur Null. Ce contrôle des valeurs Null est effectué chaque fois qu'un type défini par l'utilisateur est rapporté du CLR, ce qui signifie que l'utilisation de la construction Transact-SQL IS NULL pour vérifier le caractère Null des types définis par l'utilisateur doit toujours fonctionner. La propriété IsNull est également utilisée par le serveur pour tester si une instance est Null. Une fois que le serveur a détermine que le type défini par l'utilisateur est Null, il peut utiliser sa gestion Null native.

La méthode get() de IsNull ne représente en aucune façon un cas spécial. Si une variable @pPoint est Null, @p.IsNull est évalué par défaut à « Null », et non à « 1 ». Cela est dû au fait que l'attribut SqlMethod(OnNullCall) de la méthode IsNull get() a par défaut la valeur « false ». L'objet étant Null, lorsque la propriété est demandée, l'objet n'est pas désérialisé, la méthode n'est pas appelée et une valeur par défaut de « Null » est renvoyée.

Exemple

Dans l'exemple suivant, la variable is_Null est privée et contient l'état de Null pour l'instance du type défini par l'utilisateur. Votre code doit maintenir une valeur appropriée pour is_Null. Le type défini par l'utilisateur doit également avoir une propriété statique nommée Null qui retourne une instance de valeur Null du type défini par l'utilisateur. Cela permet au type défini par l'utilisateur de renvoyer une valeur Null si l'instance est en effet Null dans la base de données.

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

Comparaison de IS NULL et de IsNull

Considérez une table qui contient le schéma Points(id int, location Point), où Point est un type défini par l'utilisateur CLR, et les requêtes suivantes :

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

Les deux requêtes retournent les ID de points avec des emplacements non-Null. Dans la Requête 1, la gestion de Null normale est utilisée et là aucune désérialisation du type défini par l'utilisateur n'est requise. La Requête 2, en revanche, doit désérialiser chaque objet non-Null et appeller le CLR pour obtenir la valeur de la propriété IsNull. Il est clair que l'utilisation de IS NULL donnera de meilleures performances et il ne devrait jamais y avoir de raison de lire la propriété IsNull d'un type défini par l'utilisateur à partir de code Transact-SQL.

Par conséquent, quelle est l'utilité de la propriété IsNull ? En premier lieu, elle est nécessaire pour déterminer si une valeur est Null à partir du code CLR. Ensuite, le serveur a besoin d'une méthode pour tester si une instance est Null ; il utilise par conséquent cette propriété. Après avoir déterminé que l'instance est Null, il peut utiliser sa gestion Null native pour la gérer.

Implémentation de la méthode Parse

Les méthodes Parse et ToString permettent d'effectuer des conversions vers et à partir de représentations de chaîne du type défini par l'utilisateur. La méthode Parse permet de convertir une chaîne en un type défini par l'utilisateur. Elle doit être déclarée comme static (ou Shared dans Visual Basic) et prendre un paramètre de type System.Data.SqlTypes.SqlString.

Le code suivant implémente la méthode Parse pour le type défini par l'utilisateur Point, qui sépare les coordonnées X et Y. La méthode Parse a un argument unique de type System.Data.SqlTypes.SqlString et suppose que les valeurs X et Y sont fournies en tant que chaîne délimitée par des virgules. L'affectation de la valeur false à l'attribut Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall empêche la méthode Parse d'être appelée à partir d'une instance Null 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;
}

Implémentation de la méthode ToString

La méthode ToString convertit le type défini par l'utilisateur Point en valeur de chaîne. Dans ce cas, la chaîne « NULL » est retournée pour une instance Null du type Point. La méthode ToString inverse la méthode Parse en utilisant un System.Text.StringBuilder pour retourner un System.String délimité par des virgules qui consiste en valeurs de coordonnées X et Y. InvokeIfReceiverIsNull ayant par défaut la valeur « false », le contrôle du caractère Null de l'instance de Point est inutile.

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

Exposition de propriétés de type défini par l'utilisateur

Le type défini par l'utilisateur Point expose des coordonnées X et Y implémentées comme propriétés en lecture-écriture publiques de type 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;
    }
}

Validation de valeurs de type défini par l'utilisateur

Lors de l'utilisation de données de type défini par l'utilisateur, le Moteur de base de données SQL Server convertit automatiquement les valeurs binaires en valeurs de type défini par l'utilisateur. Ce processus de conversion nécessite de vérifier que les valeurs sont adaptées au format de sérialisation du type et de s'assurer que la valeur peut être désérialisée correctement. Cela permet de garantir que la valeur peut être reconvertie au format binaire. Dans le cas des types définis par l'utilisateur ordonnés par octet, cela permet de s'assurer également que la valeur binaire résultante correspond à la valeur binaire d'origine. Cela empêche des valeurs non valides d'être rendues persistantes dans la base de données. Dans certains cas, ce niveau de contrôle peut être inadéquat. Une validation supplémentaire peut être requise lorsque les valeurs de type défini par l'utilisateur doivent se trouver dans un domaine ou une plage attendu(e). Par exemple, un type défini par l'utilisateur qui implémente une date peut exiger que la valeur de jour soit un nombre positif compris dans une certaine plage de valeurs valides.

La propriété Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName de l'attribut Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute vous permet de fournir le nom d'une méthode de validation exécutée par le serveur lorsque des données sont assignées à un type défini par l'utilisateur ou converties en un type défini par l'utilisateur. ValidationMethodName est également appelé pendant l'exécution de l'utilitaire bcp et des opérations BULK INSERT, DBCC CHECKDB, DBCC CHECKFILEGROUP, DBCC CHECKTABLE, requête distribuée et RPC (Remote Procedure Call) TDS (Tabular Data Stream). La valeur par défaut de ValidationMethodName est Null, ce qui indique qu'il n'y a aucune méthode de validation.

Exemple

Le fragment de code suivant illustre la déclaration de la classe Point, qui spécifie 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 une méthode de validation est spécifiée, elle doit avoir une signature qui ressemble au fragment de code suivant.

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

La méthode de validation peut avoir une portée quelconque et doit retourner true si la valeur est valide et false dans le cas contraire. Si la méthode retourne false ou lève une exception, la valeur est traitée comme non valide et une erreur est déclenchée.

Dans l'exemple ci-dessous, le code autorise uniquement des valeurs de zéro ou plus pour les coordonnées X et 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;
    }
}

Limitations de méthode de validation

Le serveur appelle la méthode de validation lorsqu'il effectue des conversions, et non lorsque des données sont insérées en définissant des propriétés individuelles ou à l'aide d'une instruction INSERT Transact-SQL.

Vous devez appeler explicitement la méthode de validation à partir d'accesseurs Set de propriété et la méthode Parse si vous souhaitez que la méthode de validation s'exécute dans toutes les situations. Cela n'est pas obligatoire, et dans certains cas peut ne pas être souhaitable.

Exemple de validation Parse

Pour garantir que la méthode ValidatePoint est appelée dans la classe Point, vous devez l'appeler à partir de la méthode Parse et à partir des procédures de propriété qui ont défini les valeurs de coordonnées X et Y. Le fragment de code suivant montre comment appeler la méthode de validation ValidatePoint à partir de la fonction 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;
}

Exemple de validation de propriété

Le fragment de code suivant montre comment appeler la méthode de validation ValidatePoint à partir des procédures de propriété définissent les coordonnées X et 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.");
        }
    }
}

Codage de méthodes UDT

Lors du codage de méthodes UDT, considérez si l'algorithme utilisé pourrait changer avec le temps. Si c'est le cas, vous pourriez envisager de créer une classe séparée pour les méthodes utilisées par votre type défini par l'utilisateur. Si l'algorithme change, vous pouvez recompiler la classe avec le nouveau code et charger l'assembly dans SQL Server sans affecter le type défini par l'utilisateur. Dans de nombreux cas, les types définis par l'utilisateur peuvent être rechargés à l'aide de l'instruction Transact-SQL ALTER ASSEMBLY, mais cela pourrait provoquer des problèmes avec les données existantes. Par exemple, le type défini par l'utilisateur Currency fourni avec l'exemple de base de données AdventureWorks2008R2 utilise une fonction ConvertCurrency pour convertir des valeurs monétaires, implémentée dans une classe séparée. Il est possible que les algorithmes de conversion puissent changer de manière imprévisible dans le futur, ou que de nouvelles fonctionnalités soient requises. La séparation de la fonction ConvertCurrency de l'implémentation du type défini par l'utilisateur Currency procure une plus grande souplesse lors de la prévision des modifications futures.

Pour plus d'informations sur la façon d'installer les exemples CLR AdventureWorks2008R2, consultez la rubrique « Installation des exemples » dans la documentation en ligne de SQL Server.

Exemple

La classe Point contient trois méthodes simples pour calculer la distance : Distance, DistanceFrom et DistanceFromXY. Chacune retourne un double qui calcule la distance de Point à zéro, la distance d'un point spécifié à Point et la distance des coordonnées X et Y spécifiées à Point. Distance et DistanceFrom appellent chacune DistanceFromXY et illustrent comment utiliser différents arguments pour chaque méthode.

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

Utilisation d'attributs SqlMethod

La classe Microsoft.SqlServer.Server.SqlMethodAttribute fournit des attributs personnalisés pouvant être utilisés pour marquer des définitions de méthode afin de spécifier le déterminisme, pour le comportement d'appel Null, et de spécifier si une méthode est un mutateur. Les valeurs par défaut de ces propriétés sont assumées et l'attribut personnalisé est utilisé uniquement lorsqu'une valeur non définie par défaut est exigée.

Notes

La classe SqlMethodAttribute hérite de la classe SqlFunctionAttribute ; par conséquent, SqlMethodAttribute hérite des champs FillRowMethodName et TableDefinition de SqlFunctionAttribute. Cela implique qu'il est possible d'écrire une méthode table, ce qui n'est pas le cas. La méthode est compilée et l'assembly est déployé, mais une erreur à propos du type de retour IEnumerable est déclenchée pendant l'exécution avec le message suivant : « La méthode, la propriété ou le champ '<nom>' de la classe '<classe>' dans l'assembly '<assembly>' possède un type de retour non valide ».

Le tableau suivant décrit quelques-unes des propriétés Microsoft.SqlServer.Server.SqlMethodAttribute pertinentes qui peuvent être utilisées dans les méthodes UDT et répertorie leurs valeurs par défaut.

  • DataAccess
    Indique si la fonction implique l'accès aux données utilisateur stockées dans l'instance locale de SQL Server. La valeur par défaut est DataAccessKind.None.

  • IsDeterministic
    Indique si la fonction produit les mêmes valeurs de sortie étant donné les mêmes valeurs d'entrée et le même état de la base de données. La valeur par défaut est false.

  • IsMutator
    Indique si la méthode provoque une modification d'état dans l'instance de type défini par l'utilisateur. La valeur par défaut est false.

  • IsPrecise
    Indique si la fonction implique des calculs imprécis, tels que des opérations à virgule flottante. La valeur par défaut est false.

  • OnNullCall
    Indique si la méthode est appelée lorsque des arguments d'entrée de référence nulle sont spécifiés. La valeur par défaut est true.

Exemple

La propriété Microsoft.SqlServer.Server.SqlMethodAttribute.IsMutator vous permet de marquer une méthode qui autorise une modification de l'état d'une instance d'un type défini par l'utilisateur. Transact-SQL ne vous permet pas de définir deux propriétés de type défini par l'utilisateur dans la clause SET d'une instruction UPDATE. Toutefois, vous pouvez avoir une méthode marquée comme mutateur qui modifie les deux membres.

Notes

Les méthodes mutateurs ne sont pas autorisés dans les requêtes. Elles peuvent être appelées uniquement dans les instructions d'assignation ou les instructions de modification de données. Si une méthode marquée comme mutateur ne retourne pas void (ou n'est pas un Sub dans Visual Basic), CREATE TYPE échoue avec une erreur.

L'instruction suivante suppose l'existence d'un type défini par l'utilisateur Triangles qui a une méthode Rotate. L'instruction UPDATE Transact-SQL suivante appelle la méthode Rotate :

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

La méthode Rotate est décorée avec le paramètre d'attribut SqlMethodIsMutator à true afin que SQL Server puisse marquer la méthode comme méthode mutateur. Le code affecte également la valeur false à OnNullCall, ce qui indique au serveur que la méthode retourne une référence Null (Nothing dans Visual Basic) si l'un des paramètres d'entrée est une référence 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);
}

Implémentation d'un type défini par l'utilisateur avec un format défini par l'utilisateur

Lors de l'implémentation d'un type défini par l'utilisateur avec un format défini par l'utilisateur, vous devez implémenter des méthodes Read et Write qui implémentent l'interface Microsoft.SqlServer.Server.IBinarySerialize pour gérer la sérialisation et désérialisation des données UDT. Vous devez également spécifier la propriété MaxByteSize de l'attribut Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.

Le type défini par l'utilisateur Currency

Le type défini par l'utilisateur Currency est fourni avec les exemples de CLR qui peuvent être installés avec SQL Server, à compter de SQL Server 2005. Pour plus d'informations sur l'installation des exemples CLR, consultez Considérations relatives à l'installation d'exemples de bases de données et d'exemples de code SQL Server.

Le type défini par l'utilisateur Currency prend en charge la gestion des sommes d'argent dans le système monétaire d'une culture particulière. Vous devez définir deux champs : un string pour CultureInfo, qui spécifie qui a publié la monnaie (en-us, par exemple) et un decimal pour CurrencyValue, la somme d'argent.

Bien qu'il ne soit pas utilisé par le serveur pour effectuer des comparaisons, le type défini par l'utilisateur Currency implémente l'interface System.IComparable, qui expose une méthode unique, System.IComparable.CompareTo. Celle-ci est utilisée du côté client dans les situations où il est souhaitable de comparer ou d'ordonner précisément des valeurs monétaire dans des cultures.

Le code qui s'exécute dans le CLR compare la culture séparément de la valeur monétaire. Pour le code Transact-SQL, les actions suivantes déterminent la comparaison :

  1. Affectez la valeur « true » à l'attribut IsByteOrdered, ce qui indique à SQL Server qu'il faut utiliser la représentation binaire persistante sur disque à des fins de comparaison.

  2. Utilisez la méthode Write pour le type défini par l'utilisateur Currency afin de déterminer la façon dont il est rendu persistant sur disque et par conséquent la façon dont les valeurs UDT sont comparées et ordonnées pour les opérations Transact-SQL.

  3. Enregistrez le type défini par l'utilisateur Currency à l'aide du format binaire suivant :

    1. Enregistrez la culture en tant que chaîne encodée UTF-16 pour les octets 0-19, avec un remplissage à droite avec des caractères Null.

    2. Utilisez les octets 20 et plus pour contenir la valeur décimale de la monnaie.

L'objectif du remplissage est de s'assurer que la culture est complètement séparée de la valeur monétaire, de sorte que lorsque deux types définis par l'utilisateur sontt comparés dans le code Transact-SQL, les octets de culture soient comparés à des octets de culture et les valeurs d'octets de monnaie soient comparées à des valeurs d'octets de monnaie.

Pour obtenir le code compler pour le type défini par l'utilisateur Currency, suivez les instructions d'installation des exemples CLR dans Considérations relatives à l'installation d'exemples de bases de données et d'exemples de code SQL Server.

Attributs Currency

Le type défini par l'utilisateur Currency est défini avec les attributs suivants :

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

Création de méthodes Read et Write avec IBinarySerialize

Lorsque vous choisissez le format de sérialisation UserDefined, vous devez également implémenter l'interface IBinarySerialize et créer vos propres méthodes Read et Write. Les procédures suivantes du type défini par l'utilisateur Currency utilisent System.IO.BinaryReader et System.IO.BinaryWriter pour lire et écrire dans le type défini par l'utilisateur.

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

Pour obtenir le code complet pour le type défini par l'utilisateur Currency, consultez Considérations relatives à l'installation d'exemples de bases de données et d'exemples de code SQL Server.

Voir aussi

Autres ressources