Condividi tramite


Creazione di tipi definiti dall'utente - Codifica

Si applica a:SQL Server

Quando si codifica la definizione del tipo definito dall'utente (UDT), è necessario implementare varie caratteristiche a seconda che il tipo UDT venga implementato come classe o come struttura, nonché a seconda delle opzioni di formato e di serializzazione scelte.

L'esempio in questa sezione illustra l'implementazione di un tipo UDT point come struct (o Struttura in Visual Basic). Il tipo definito dall'utente del punto è costituito da coordinate X e Y implementate come routine di proprietà.

Quando si definisce un tipo definito dall'utente sono richiesti gli spazi dei nomi seguenti:

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

Lo spazio dei nomi Microsoft.SqlServer.Server contiene gli oggetti necessari per vari attributi del tipo definito dall'utente e lo spazio dei nomi System.Data.SqlTypes contiene le classi che rappresentano SQL Server tipi di dati nativi disponibili per l'assembly. Naturalmente, per il corretto funzionamento dell'assembly potrebbero essere necessari altri spazi dei nomi. Il tipo definito dall'utente del punto usa anche lo spazio dei nomi System.Text per l'utilizzo delle stringhe.

Nota

Gli oggetti di database visual C++, ad esempio i tipi definiti dall'utente, compilati con /clr:pure non sono supportati per l'esecuzione.

Specifica degli attributi

Gli attributi consentono di determinare la modalità di utilizzo della serializzazione per costruire la rappresentazione di archiviazione dei tipi definiti dall'utente e per trasmettere tali tipi al client in base al valore.

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute è obbligatorio. L'attributo Serializable è facoltativo. È anche possibile specificare Microsoft.SqlServer.Server.SqlFacetAttribute per fornire informazioni sul tipo restituito di un tipo definito dall'utente. Per altre informazioni, vedere Attributi personalizzati per routine CLR.

Attributi per il tipo definito dall'utente Point

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute imposta il formato di archiviazione per il tipo definito dall'utente del punto su Nativo. IsByteOrdered è impostato su true, che garantisce che i risultati dei confronti siano uguali in SQL Server come se lo stesso confronto fosse stato eseguito nel codice gestito. Il tipo definito dall'utente implementa l'interfaccia System.Data.SqlTypes.INullable per rendere il tipo UDT null compatibile.

Il frammento di codice seguente mostra gli attributi per il tipo definito dall'utente del 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  
{  

Implementazione del supporto dei valori Null

Oltre a specificare gli attributi per gli assembly in modo corretto, il tipo definito dall'utente deve supportare anche i valori Null. I tipi definiti dall'utente caricati in SQL Server sono in grado di riconoscere null, ma affinché il tipo definito dall'utente riconosca un valore Null, il tipo definito dall'utente deve implementare l'interfaccia System.Data.SqlTypes.INullable.

È necessario creare una proprietà denominata IsNull, necessaria per determinare se un valore è Null all'interno del codice CLR. Quando SQL Server trova un'istanza Null di un tipo definito dall'utente, il tipo definito dall'utente viene reso persistente usando i normali metodi di gestione dei valori Null. Se non è necessario, nel server non viene eseguita la serializzazione o la deserializzazione del tipo definito dall'utente. Non viene inoltre occupato spazio per l'archiviazione di un tipo definito dall'utente Null. Questo controllo dei valori Null viene eseguito ogni volta che un tipo definito dall'utente viene spostato da CLR, il che significa che l'uso del costrutto Transact-SQL IS NULL per verificare la presenza di tipi definiti dall'utente Null deve sempre funzionare. La proprietà IsNull viene utilizzata anche dal server per verificare se un'istanza è Null. Una volta stabilito che il tipo definito dall'utente è Null, il server è in grado di utilizzare la relativa funzionalità di gestione nativa dei valori Null.

Il metodo get() di IsNull non viene fatta in alcun modo con maiuscole e minuscole. Se una variabile Point@p è Null, @p.IsNull restituirà per impostazione predefinita "NULL", non "1". Il motivo è che l'attributo SqlMethod(OnNullCall) del metodo IsNull get() viene impostato su false. Poiché l'oggetto è Null, quando viene richiesta la proprietà l'oggetto non viene deserializzato, il metodo non viene chiamato e viene restituito un valore predefinito "NULL".

Esempio

Nell'esempio seguente la variabile is_Null è privata e mantiene lo stato Null per l'istanza del tipo definito dall'utente. Il codice deve gestire un valore appropriato per is_Null. Il tipo definito dall'utente deve avere anche una proprietà statica denominata Null che restituisce un'istanza di valore Null del tipo definito dall'utente. In questo modo il tipo definito dall'utente può restituire un valore Null se l'istanza è Null nel database.

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

Confronto tra IS NULL e IsNull

Si consideri una tabella contenente lo schema Points(id int, location Point), dove Point è un tipo CLR definito dall'utente e le query seguenti:

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

Entrambe le query restituiscono gli ID dei punti con posizioni non Null . Nella Query 1 viene utilizzata la normale gestione dei valori Null e non è necessaria la deserializzazione dei tipi definiti dall'utente. La query 2, invece, deve deserializzare ogni oggetto non Null e chiamare in CLR per ottenere il valore della proprietà IsNull . Chiaramente, l'uso di IS NULL mostrerà prestazioni migliori e non dovrebbe mai esserci un motivo per leggere la proprietà IsNull di un tipo definito dall'utente dal codice Transact-SQL.

Quindi, qual è l'uso della proprietà IsNull ? Prima di tutto, è necessario determinare se un valore è Null all'interno del codice CLR. In secondo luogo, il server necessita di un modo per verificare se un'istanza è Null, quindi questa proprietà viene usata dal server. Dopo aver determinato che è Null, può usare la gestione nativa dei valori Null per gestirla.

Implementazione del metodo Parse

I metodi Parse e ToString consentono le conversioni da e verso le rappresentazioni di stringa del tipo definito dall'utente. Il metodo Parse consente di convertire una stringa in un tipo definito dall'utente. Deve essere dichiarato come statico (o Condiviso in Visual Basic) e accettare un parametro di tipo System.Data.SqlTypes.SqlString.

Il codice seguente implementa il metodo Parse per il tipo definito dall'utente point, che separa le coordinate X e Y. Il metodo Parse ha un singolo argomento di tipo System.Data.SqlTypes.SqlString e presuppone che i valori X e Y vengano forniti come stringa delimitata da virgole. L'impostazione dell'attributo Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall su false impedisce che il metodo Parse venga chiamato da un'istanza null di 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;  
}  

Implementazione del metodo ToString

Il metodo ToString converte point UDT in un valore stringa. In questo caso, la stringa "NULL" viene restituita per un'istanza Null del tipo Point . Il metodo ToString inverte il metodo Parse usando un System.Text.StringBuilder per restituire un System.String delimitato da virgole costituito dai valori delle coordinate X e Y. Poiché l'impostazione predefinita InvokeIfReceiverIsNull è false, il controllo della presenza di un'istanza Null di Point non è necessario.

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

Esposizione delle proprietà dei tipi definiti dall'utente

Il tipo definito dall'utente point espone le coordinate X e Y implementate come proprietà pubbliche di lettura/scrittura di 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;  
    }  
}  

Convalida dei valori dei tipi definiti dall'utente

Quando si utilizzano i dati definiti dall'utente, SQL Server motore di database converte automaticamente i valori binari in valori definiti dall'utente. Ai fini di tale processo di conversione, viene verificato che i valori siano appropriati al formato di serializzazione del tipo e che il valore possa essere deserializzato correttamente. In questo modo si garantisce che il valore possa essere convertito di nuovo in formato binario. Nel caso dei tipi definiti dall'utente ordinati per byte, questo processo assicura anche che il valore binario risultante corrisponda al valore binario originale. In questo modo si impedisce che valori non validi vengano resi persistenti nel database. In alcuni casi, questo livello di controllo può risultare inadeguato. Una convalida aggiuntiva può essere necessaria quando è necessario che i valori dei tipi definiti dall'utente si trovino in un dominio o in un intervallo previsto. Un tipo definito dall'utente che implementa, ad esempio, una data potrebbe richiedere che il valore del giorno sia un numero positivo compreso in un determinato intervallo di valori validi.

La proprietà Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName della proprietà Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute consente di specificare il nome di un metodo di convalida eseguito dal server quando i dati vengono assegnati a un tipo definito dall'utente o convertiti in un tipo definito dall'utente. ValidationMethodName viene chiamato anche durante l'esecuzione dell'utilità bcp, BULK INSERT, DBCC CHECKDB, DBCC CHECKFILEGROUP, DBCC CHECKTABLE, query distribuita e operazioni rpc (Remote Procedure Call) del flusso di dati tabulare . Il valore predefinito per ValidationMethodName è Null, a indicare che non è presente alcun metodo di convalida.

Esempio

Il frammento di codice seguente mostra la dichiarazione per la classe Point , che specifica un oggetto ValidationMethodName di 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  
{  

Se si specifica un metodo di convalida, è necessario che includa una firma simile al frammento di codice seguente:

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

Il metodo di convalida può avere qualsiasi ambito e deve restituire true se il valore è valido e false in caso contrario. Se il metodo restituisce false o genera un'eccezione, il valore viene considerato non valido e viene generato un errore.

Nell'esempio riportato di seguito il codice accetta solo valori pari a zero o superiori per le coordinate 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;  
    }  
}  

Limitazioni del metodo di convalida

Il server chiama il metodo di convalida quando il server esegue conversioni, non quando i dati vengono inseriti impostando singole proprietà o quando i dati vengono inseriti usando un'istruzione Transact-SQL INSERT.

È necessario chiamare in modo esplicito il metodo di convalida dai setter di proprietà e dal metodo Parse se si desidera che il metodo di convalida venga eseguito in tutte le situazioni. Questa operazione non è obbligatoria e in alcuni casi può essere anche sconsigliata.

Esempio di convalida del metodo Parse

Per assicurarsi che il metodo ValidatePoint venga richiamato nella classe Point , è necessario chiamarlo dal metodo Parse e dalle routine delle proprietà che impostano i valori delle coordinate X e Y. Il frammento di codice seguente mostra come chiamare il metodo di convalida ValidatePoint dalla funzione 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;  
}  

Esempio di convalida delle proprietà

Il frammento di codice seguente illustra come chiamare il metodo di convalida ValidatePoint dalle routine di proprietà che impostano le coordinate 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.");  
        }  
    }  
}  

Codifica dei metodi UDT

Quando si codificano i metodi UDT, è consigliabile valutare la possibilità che l'algoritmo utilizzato possa cambiare nel tempo. In questo caso è possibile creare una classe separata per i metodi utilizzati dal tipo definito dall'utente. Se l'algoritmo cambia, è possibile ricompilare la classe con il nuovo codice e caricare l'assembly in SQL Server senza influire sul tipo definito dall'utente. In molti casi è possibile ricaricare i tipi definiti dall'utente usando l'istruzione Transact-SQL ALTER ASSEMBLY, ma ciò potrebbe causare problemi con i dati esistenti. Ad esempio, il tipo definito dall'utente di valuta incluso nel database di esempio AdventureWorks usa una funzione ConvertCurrency per convertire i valori di valuta, implementati in una classe separata. È possibile che gli algoritmi di conversione cambino nel tempo in modo imprevedibile o che venga richiesta una nuova funzionalità. La separazione della funzione ConvertCurrency dall'implementazione di Currency UDT offre una maggiore flessibilità durante la pianificazione delle modifiche future.

Esempio

La classe Point contiene tre semplici metodi per calcolare la distanza: Distance,DistanceFromxY e DistanceFromXY. Ogni restituisce un doppio calcolo della distanza da Punto a zero, la distanza da un punto specificato a Point e la distanza dalle coordinate X e Y specificate a Point. Distanza e DistanzaFrom ogni chiamata DistanceFromXY e illustra come usare argomenti diversi per ogni metodo.

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

Utilizzo degli attributi SqlMethod

La classe Microsoft.SqlServer.Server.SqlMethodAttribute fornisce attributi personalizzati che possono essere usati per contrassegnare le definizioni dei metodi per specificare determinism, in comportamento di chiamata Null e per specificare se un metodo è un mutatore. Per queste proprietà si presuppone l'uso dei valori predefiniti e l'attributo personalizzato viene utilizzato solo quando è necessario un valore non predefinito.

Nota

La classe SqlMethodAttribute eredita dalla classe SqlFunctionAttribute , quindi SqlMethodAttribute eredita i campi FillRowMethodName e TableDefinition da SqlFunctionAttribute. Questo implica, contrariamente al vero, la possibilità di scrivere un metodo con valori di tabella. Il metodo compila e l'assembly viene distribuito, ma viene generato un errore relativo al tipo restituito IEnumerable in fase di esecuzione con il messaggio seguente: "Metodo, proprietà o campo 'name>' nella classe '<class>' nell'assembly '<<assembly>' non è valido tipo restituito".

La tabella seguente descrive alcune delle proprietà Microsoft.SqlServer.Server.SqlMethodAttribute pertinenti che possono essere usate nei metodi UDT e elenca i relativi valori predefiniti.

DataAccess
Indica se la funzione comporta l'accesso ai dati dell'utente memorizzati nell'istanza locale di SQL Server. Il valore predefinito è DataAccessKind. Nessuno.

IsDeterministic
Indica se la funzione produce gli stessi valori di output quando vengono specificati gli stessi valori di input e lo stesso stato del database. Il valore predefinito è false.

IsMutator
Indica se il metodo causa una modifica dello stato dell'istanza UDT. Il valore predefinito è false.

IsPrecise
Indica se la funzione comporta calcoli imprecisi, quali operazioni a virgola mobile. Il valore predefinito è false.

OnNullCall
Indica se il metodo viene chiamato quando vengono specificati argomenti di input con riferimento Null. Il valore predefinito è true.

Esempio

La proprietà Microsoft.SqlServer.Server.SqlMethodAttribute.IsMutator consente di contrassegnare un metodo che consente una modifica nello stato di un'istanza di un'istanza di un oggetto UDT. Transact-SQL non consente di impostare due proprietà UDT nella clausola SET di un'istruzione UPDATE. È tuttavia possibile contrassegnare un metodo come mutatore che modifica i due membri.

Nota

Nelle query non è consentito l'uso di metodi di tipo mutatore. Tali metodi possono essere chiamati solo nelle istruzioni di assegnazione o nelle istruzioni di modifica dei dati. Se un metodo contrassegnato come mutator non restituisce void (o non è un sub in Visual Basic), CREATE TYPE ha esito negativo con un errore.

L'istruzione seguente presuppone l'esistenza di un oggetto Triangles UDT con un metodo Rotate . L'istruzione di aggiornamento Transact-SQL seguente richiama il metodo Rotate :

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

Il metodo Rotate viene decorato con l'impostazione dell'attributo SqlMethodsutrue in modo che SQL Server possa contrassegnare il metodo come metodo mutator. Il codice imposta anche OnNullCall su false, che indica al server che il metodo restituisce un riferimento Null (Nothing in Visual Basic) se uno dei parametri di input sono riferimenti 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);  
}  

Implementazione di un tipo definito dall'utente con un formato definito dall'utente

Quando si implementa un oggetto UDT con un formato definito dall'utente, è necessario implementare metodi di lettura e scrittura che implementano l'interfaccia Microsoft.SqlServer.IBinarySerialize per gestire la serializzazione e la deserializzazione dei dati UDT. È anche necessario specificare la proprietà MaxByteSize di Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.

Tipo definito dall'utente Currency

L'UDT valuta è incluso negli esempi CLR che possono essere installati con SQL Server, a partire da SQL Server 2005 (9,x).

L'UUDT valuta supporta la gestione di importi di denaro nel sistema monetario di una determinata cultura. È necessario definire due campi: una stringa per CultureInfo, che specifica chi ha emesso la valuta (en-us, ad esempio) e un decimale per CurrencyValue, la quantità di denaro.

Anche se non viene usato dal server per eseguire confronti, l'oggetto Currency UDT implementa l'interfaccia System.IComparable , che espone un singolo metodo , System.IComparable.CompareTo. Tale metodo viene utilizzato sul lato client nelle situazioni in cui è consigliabile confrontare o ordinare i valori relativi alla valuta in modo accurato all'interno delle diverse impostazioni cultura.

Il codice eseguito in CLR confronta le impostazioni cultura separatamente dal valore della valuta. Per il codice Transact-SQL, le azioni seguenti determinano il confronto:

  1. Impostare l'attributo IsByteOrdered su true, che indica SQL Server di usare la rappresentazione binaria persistente sul disco per i confronti.

  2. Usare il metodo Write per l'UDT Valuta per determinare come l'UDT è persistente sul disco e quindi come vengono confrontati e ordinati i valori dell'utente per le operazioni Transact-SQL.

  3. Salvare l'UDT valuta usando il formato binario seguente:

    1. Salvare le impostazioni cultura come stringa codificata UTF-16 per i byte 0-19 con riempimento a destra con caratteri Null.

    2. Utilizzare i byte 20 e successivi per contenere il valore decimale della valuta.

Lo scopo della spaziatura interna consiste nel garantire che le impostazioni cultura siano completamente separate dal valore di valuta, in modo che quando un oggetto UDT viene confrontato con un altro nel codice Transact-SQL, i byte delle impostazioni cultura vengono confrontati con i byte cultura e i valori di byte valuta vengono confrontati con i valori di byte valuta.

Attributi di Currency

L'oggetto Currency UDT viene definito con gli attributi seguenti.

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

Creazione di metodi di lettura e scrittura con IBinarySerialize

Quando si sceglie il formato di serializzazione UserDefined , è necessario implementare anche l'interfaccia IBinarySerialize e creare metodi di lettura e scrittura personalizzati. Le procedure seguenti dall'UUDT valuta usano System.IO.BinaryReader e System.IO.BinaryWriter per leggere e scrivere nell'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();  
}  

Vedere anche

Creazione di un tipo di User-Defined