Création de types définis par l’utilisateur - Codage

S’applique à :SQL Server

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 point UDT en tant que struct (ou structure en Visual Basic). L’UDT de point se compose de coordonnées X et Y implémentées en tant que 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 les différents attributs de votre UDT, et l’espace de noms System.Data.SqlTypes contient les classes qui représentent SQL Server types de données natifs disponibles pour l’assembly. Il se peut bien sûr que le bon fonctionnement de votre assembly requiert des espaces de noms supplémentaires. L’UDT point utilise également l’espace de noms System.Text pour travailler avec des chaînes.

Notes

Les objets de base de données Visual C++, tels que les UDT, 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.

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute est obligatoire. L’attribut Serializable est facultatif. Vous pouvez également spécifier Microsoft.SqlServer.Server.SqlFacetAttribute pour fournir des informations sur le type de retour d’un UDT. Pour plus d’informations, consultez Attributs personnalisés pour les routines CLR

Attributs du type défini par l'utilisateur Point

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute définit le format de stockage de l’UDT point sur Native. IsByteOrdered a la valeur true, ce qui garantit que les résultats des comparaisons sont les mêmes dans SQL Server que si la même comparaison avait eu lieu dans le code managé. L’UDT implémente l’interface System.Data.SqlTypes.INullable pour rendre l’UDT null sensible.

Le fragment de code suivant montre les attributs de l’UDT 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 UDT chargés dans SQL Server prennent en charge la valeur Null, mais pour que l’UDT reconnaisse une valeur null, l’UDT doit implémenter l’interface System.Data.SqlTypes.INullable.

Vous devez créer une propriété nommée IsNull, qui est nécessaire pour déterminer si une valeur est null à partir du code CLR. Quand SQL Server trouve une instance null d’un UDT, l’UDT est conservé à l’aide des méthodes de gestion null normales. 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. Cette case activée pour les valeurs null est effectuée chaque fois qu’un UDT est importé à partir du CLR, ce qui signifie que l’utilisation de la construction Transact-SQL IS NULL pour case activée pour les UDT null doit toujours fonctionner. La propriété IsNull est également utilisée par le serveur pour tester si un instance a la valeur 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()d’IsNull n’est en aucun cas spécial. Si une variable Point@p a la valeur Null, @p.IsNull est, par défaut, évalué à « NULL », et non à « 1 ». Cela est dû au fait que l’attribut SqlMethod(OnNullCall) de la méthode IsNull get() a la valeur par défaut false. Étant donné que l’objet a la valeur 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 « NULL » est retourné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. L’UDT doit également avoir une propriété statique nommée Null qui retourne une valeur null instance de l’UDT. 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 UDT 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, quant à elle, doit désérialiser chaque objet non Null et appeler le CLR pour obtenir la valeur de la propriété IsNull . Il est clair que l’utilisation de la valeur IS NULL présente de meilleures performances et qu’il ne devrait jamais y avoir de raison de lire la propriété IsNull d’un UDT à partir du code Transact-SQL.

Quelle est l’utilisation de la propriété IsNull ? Tout d’abord, il est nécessaire de déterminer si une valeur est Null à partir du code CLR. Deuxièmement, le serveur a besoin d’un moyen de tester si un instance a la valeur Null. Cette propriété est donc utilisée par le serveur. Une fois qu’il a déterminé qu’il est Null, il peut utiliser sa gestion null native pour le gérer.

Implémentation de la méthode Parse

Les méthodes Parse et ToString permettent les conversions vers et à partir des représentations sous forme de chaîne de l’UDT. La méthode Parse permet de convertir une chaîne en un UDT. Il doit être déclaré statique (ou partagé en Visual Basic) et prendre un paramètre de type System.Data.SqlTypes.SqlString.

Le code suivant implémente la méthode Parse pour l’UDT 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 sous forme de chaîne délimitée par des virgules. La définition de l’attribut Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall sur false empêche l’appel de la méthode Parse à partir d’un 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 l’UDT 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 à l’aide d’un System.Text.StringBuilder pour renvoyer un System.String délimité par des virgules composé des valeurs de coordonnées X et Y. Étant donné qu’InvokeIfReceiverIsNull a la valeur false par défaut, la case activée d’une instance null de Point n’est pas nécessaire.

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

L’UDT point expose les coordonnées X et Y qui sont implémentées en tant que propriétés publiques en lecture-écriture 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

Lorsque vous utilisez des données UDT, SQL Server Moteur de base de données convertit automatiquement les valeurs binaires en valeurs UDT. 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 Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute vous permet de fournir le nom d’une méthode de validation que le serveur exécute lorsque des données sont affectées à un UDT ou converties en UDT. ValidationMethodName est également appelé lors de l’exécution de l’utilitaire bcp, BULK INSERT, DBCC CHECKDB, DBCC CHECKFILEGROUP, DBCC CHECKTABLE, de requête distribuée et d’appels de procédure à distance (RPC) de flux de données tabulaires (TDS). La valeur par défaut de ValidationMethodName est null, ce qui indique qu’il n’existe aucune méthode de validation.

Exemple

Le fragment de code suivant montre 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 n’importe quelle étendue 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 généré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 lorsque le serveur effectue des conversions, et non lorsque des données sont insérées en définissant des propriétés individuelles ou lorsque des données sont insérées à l’aide d’une instruction TRANSACT-SQL INSERT.

Vous devez appeler explicitement la méthode de validation à partir de setters de propriétés 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 vous assurer que la méthode ValidatePoint est appelée dans la classe Point , vous devez l’appeler à partir de la méthode Parse et des procédures de propriété qui définissent 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é qui 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 l’UDT. Dans de nombreux cas, les UDT peuvent être rechargés à l’aide de l’instruction Transact-SQL ALTER ASSEMBLY, mais cela peut entraîner des problèmes avec les données existantes. Par exemple, l’UDT currency inclus dans l’exemple de base de données AdventureWorks utilise une fonction ConvertCurrency pour convertir des valeurs monétaires, qui est implémentée dans une classe distincte. 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 UDT Currency offre une plus grande flexibilité lors de la planification des modifications futures.

Exemple

La classe Point contient trois méthodes simples pour calculer la distance : Distance, DistanceFrom et DistanceFromXY. Chaque retourne un double calculant la distance entre point et zéro, la distance entre un point spécifié et point et la distance entre les coordonnées X et Y spécifiées et point. Distance et DistanceFrom appellent distanceFromXY et montrent 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 qui peuvent être utilisés pour marquer des définitions de méthode afin de spécifier le déterminisme, sur 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 , de sorte que 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 se compile et l’assembly se déploie, mais une erreur concernant le type de retour IEnumerable est générée lors de l’exécution avec le message suivant : « La méthode, la propriété ou le champ '<name>' in class' in class<>' in assembly '<assembly>' has invalid return type ».

Le tableau suivant décrit certaines 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. Aucune.

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’un instance d’un type défini par l’utilisateur. Transact-SQL ne vous permet pas de définir deux propriétés UDT 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 en Visual Basic), CREATE TYPE échoue avec une erreur.

L’instruction suivante suppose l’existence d’un UDT Triangles qui a une méthode Rotate . L’instruction de mise à jour 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 l’attribut SqlMethod définissant IsMutator sur true afin que SQL Server puissiez marquer la méthode en tant que méthode mutateur. Le code définit également OnNullCall sur false, ce qui indique au serveur que la méthode retourne une référence Null (Nothing en Visual Basic) si l’un des paramètres d’entrée est des références 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 de lecture et d’écriture qui implémentent l’interface Microsoft.SqlServer.Server.IBinarySerialize pour gérer la sérialisation et la désérialisation des données UDT. Vous devez également spécifier la propriété MaxByteSize de Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.

Le type défini par l'utilisateur Currency

L’UDT Currency est inclus dans les exemples CLR qui peuvent être installés avec SQL Server, à compter de SQL Server 2005 (9.x).

L’UDT monétaire prend en charge la gestion des montants d’argent dans le système monétaire d’une culture particulière. Vous devez définir deux champs : une chaîne pour CultureInfo, qui spécifie qui a émis la devise (en-us, par exemple) et une décimale pour CurrencyValue, le montant de l’argent.

Bien qu’il ne soit pas utilisé par le serveur pour effectuer des comparaisons, l’UDT 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. Définissez l’attribut IsByteOrdered sur true, ce qui indique à SQL Server d’utiliser la représentation binaire persistante sur le disque pour les comparaisons.

  2. Utilisez la méthode Write pour l’UDT Currency afin de déterminer comment le type défini par l’utilisateur est conservé sur le disque et, par conséquent, comment les valeurs UDT sont comparées et triées pour les opérations Transact-SQL.

  3. Enregistrez l’UDT Currency au 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 lorsqu’un type défini par l’utilisateur est comparé à un autre dans le code Transact-SQL, les octets de culture sont comparés aux octets de culture, et les valeurs d’octets monétaires sont comparées aux valeurs d’octets monétaires.

Attributs Currency

L’UDT 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 de lecture et d’écriture . Les procédures suivantes de l’UDT Currency utilisent System.IO.BinaryReader et System.IO.BinaryWriter pour lire et écrire dans l’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();  
}  

Voir aussi

Création d’un type User-Defined