Tworzenie typów zdefiniowanych przez użytkownika za pomocą ADO.NET
Dotyczy:programu SQL Server
Podczas kodowania definicji typu zdefiniowanego przez użytkownika (UDT) należy zaimplementować różne funkcje w zależności od tego, czy implementujesz funkcję UDT jako klasę, czy strukturę, oraz opcje formatowania i serializacji.
W przykładzie w tej sekcji przedstawiono implementację Point
UDT jako struct
(lub Structure
w Visual Basic).
Point
UDT składa się ze współrzędnych X i Y implementowanych jako procedury właściwości.
Podczas definiowania funkcji UDT wymagane są następujące przestrzenie nazw:
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
Przestrzeń nazw Microsoft.SqlServer.Server
zawiera obiekty wymagane dla różnych atrybutów udT, a System.Data.SqlTypes
przestrzeni nazw zawiera klasy reprezentujące natywne typy danych programu SQL Server dostępne dla zestawu. Mogą istnieć inne przestrzenie nazw wymagane przez zestaw w celu poprawnego działania.
Point
udT używa również System.Text
przestrzeni nazw do pracy z ciągami.
Nuta
Obiekty bazy danych Visual C++, takie jak UTS, skompilowane przy użyciu /clr:pure
nie są obsługiwane do wykonywania.
Określanie atrybutów
Atrybuty określają sposób użycia serializacji do konstruowania reprezentacji magazynu tras ZDEFINIOWANYch przez użytkownika i przesyłania tras zdefiniowanych przez wartość do klienta.
Wymagany jest Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute
. Atrybut Serializable
jest opcjonalny. Można również określić Microsoft.SqlServer.Server.SqlFacetAttribute
, aby podać informacje o zwracanym typie udT. Aby uzyskać więcej informacji, zobacz integracja środowiska CLR: atrybuty niestandardowe dla procedur CLR.
Atrybuty udT punktu
Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute
ustawia format magazynu dla Point
UDT na Native
.
IsByteOrdered
jest ustawiona na true
, co gwarantuje, że wyniki porównań są takie same w programie SQL Server, jak w przypadku tego samego porównania w kodzie zarządzanym. Funkcja UDT implementuje interfejs System.Data.SqlTypes.INullable
, aby umożliwić rozpoznawanie wartości null przez funkcję UDT.
Poniższy fragment kodu przedstawia atrybuty Point
UDT.
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,
IsByteOrdered=true)]
public struct Point : INullable { ... }
Implementowanie wartości null
Oprócz poprawnego określania atrybutów zestawów funkcja UDT musi również obsługiwać wartość null. Trasy zdefiniowane przez użytkownika załadowane do programu SQL Server mają wartość null, ale aby funkcja UDT rozpoznawała wartość null, funkcja UDT musi zaimplementować interfejs System.Data.SqlTypes.INullable
.
Należy utworzyć właściwość o nazwie IsNull
, która jest wymagana do określenia, czy wartość ma wartość null z poziomu kodu CLR. Gdy program SQL Server znajdzie wystąpienie funkcji UDT o wartości null, funkcja UDT jest utrwalana przy użyciu normalnych metod obsługi wartości null. Serwer nie traci czasu serializacji ani deserializacji udT, jeśli nie musi, i nie traci miejsca do przechowywania null UDT. Ta kontrola wartości null jest wykonywana za każdym razem, gdy funkcja UDT zostanie przeniesiona ze środowiska CLR, co oznacza, że użycie konstrukcji Transact-SQL IS NULL
w celu sprawdzenia wartości null zdefiniowanych przez użytkownika powinno zawsze działać. Właściwość IsNull
jest również używana przez serwer do testowania, czy wystąpienie ma wartość null. Gdy serwer ustali, że funkcja UDT ma wartość null, może użyć natywnej obsługi wartości null.
Metoda get()
IsNull
nie jest w żaden sposób specjalna. Jeśli zmienna Point
@p
@p
jest Null
, @p.IsNull
domyślnie będzie oceniać NULL
, a nie 1
. Dzieje się tak, ponieważ atrybut SqlMethod(OnNullCall)
metody IsNull get()
domyślnie ma wartość false. Ponieważ obiekt jest Null
, gdy właściwość jest żądana, obiekt nie jest deserializowany, metoda nie jest wywoływana, a zwracana jest domyślna wartość "NULL".
Przykład
W poniższym przykładzie zmienna is_Null
jest prywatna i przechowuje stan null dla wystąpienia funkcji UDT. Kod musi zachować odpowiednią wartość dla is_Null
. Funkcja UDT musi również mieć właściwość statyczną o nazwie Null
, która zwraca wystąpienie wartości null udT. Dzięki temu funkcja UDT zwraca wartość null, jeśli wystąpienie rzeczywiście ma wartość null w bazie danych.
private bool is_Null;
public bool IsNull
{
get
{
return (is_Null);
}
}
public static Point Null
{
get
{
Point pt = new Point();
pt.is_Null = true;
return pt;
}
}
IS NULL a IsNull
Rozważmy tabelę zawierającą Points(id int, location Point)
schematu, gdzie Point
jest clR UDT i następującymi zapytaniami:
Kwerenda 1:
SELECT ID FROM Points WHERE NOT (location IS NULL); -- Or, WHERE location IS NOT NULL;
Zapytanie 2:
SELECT ID FROM Points WHERE location.IsNull = 0;
Oba zapytania zwracają identyfikatory punktów z lokalizacjami innych niż null. W zapytaniu 1 używana jest normalna obsługa wartości null i nie jest wymagana deserializacja tras ZDEFINIOWANYch przez użytkownika. Zapytanie 2, z drugiej strony, musi wykonać deserializacji każdego obiektu innego niż null i wywołać do CLR, aby uzyskać wartość właściwości IsNull
. Oczywiście użycie IS NULL
wykazuje lepszą wydajność i nigdy nie powinno być powodem do odczytania właściwości IsNull
udT z kodu Transact-SQL.
Co to jest użycie właściwości IsNull
? Najpierw należy określić, czy wartość ma wartość null z poziomu kodu CLR. Po drugie serwer musi sprawdzić, czy wystąpienie ma wartość null, więc ta właściwość jest używana przez serwer. Po ustaleniu, że ma wartość null, może ona obsługiwać ją przy użyciu natywnej obsługi wartości null.
Implementowanie metody analizy
Metody Parse
i ToString
umożliwiają konwersje na i z reprezentacji ciągów udT. Metoda Parse
umożliwia konwertowanie ciągu na udT. Musi być zadeklarowany jako static
(lub Shared
w Visual Basic) i mieć parametr typu System.Data.SqlTypes.SqlString
.
Poniższy kod implementuje metodę Parse
dla Point
UDT, która oddziela współrzędne X i Y. Metoda Parse
ma jeden argument typu System.Data.SqlTypes.SqlString
i zakłada, że wartości X i Y są dostarczane jako ciąg rozdzielany przecinkami. Ustawienie atrybutu Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall
na wartość false
uniemożliwia wywoływanie metody Parse
z wystąpienia punktu o wartości null.
[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;
}
Implementowanie metody ToString
Metoda ToString
konwertuje Point
UDT na wartość ciągu. W tym przypadku ciąg "NULL" jest zwracany dla wystąpienia wartości Null typu Point
. Metoda ToString
odwraca metodę Parse
przy użyciu System.Text.StringBuilder
, aby zwrócić rozdzielane przecinkami System.String
składające się z wartości współrzędnych X i Y. Ponieważ InvokeIfReceiverIsNull
wartością domyślną jest fałsz, sprawdzanie wystąpienia wartości null Point
jest niepotrzebne.
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();
}
}
Uwidacznianie właściwości udT
Point
UDT uwidacznia współrzędne X i Y, które są implementowane jako publiczne właściwości odczytu i zapisu typu System.Int32
.
public Int32 X
{
get
{
return this._x;
}
set
{
_x = value;
}
}
public Int32 Y
{
get
{
return this._y;
}
set
{
_y = value;
}
}
Weryfikowanie wartości UDT
Podczas pracy z danymi UDT aparat bazy danych programu SQL Server automatycznie konwertuje wartości binarne na wartości UDT. Ten proces konwersji obejmuje sprawdzenie, czy wartości są odpowiednie dla formatu serializacji typu i upewnienia się, że wartość może być poprawnie deserializowana. Dzięki temu można przekonwertować wartość z powrotem na formę binarną. W przypadku bajtów uporządkowanych przez użytkownika, zapewnia to również, że wynikowa wartość binarna jest zgodna z oryginalną wartością binarną. Zapobiega to utrwalaniu nieprawidłowych wartości w bazie danych. W niektórych przypadkach ten poziom kontroli może być niewystarczający. Dodatkowa weryfikacja może być wymagana, gdy wartości UDT muszą znajdować się w oczekiwanej domenie lub zakresie. Na przykład funkcja UDT, która implementuje datę, może wymagać, aby wartość dnia mogła być liczbą dodatnią, która mieści się w określonym zakresie prawidłowych wartości.
Właściwość Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName
Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute
umożliwia podanie nazwy metody weryfikacji, która jest uruchamiana przez serwer, gdy dane są przypisane do funkcji UDT lub konwertowane na udT. ValidationMethodName
ma wartość null, co oznacza, że nie ma metody sprawdzania poprawności.
Przykład
Poniższy fragment kodu przedstawia deklarację klasy Point
, która określa ValidationMethodName
ValidatePoint
.
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,
IsByteOrdered=true,
ValidationMethodName = "ValidatePoint")]
public struct Point : INullable { ... }
Jeśli określono metodę weryfikacji, musi ona mieć podpis podobny do poniższego fragmentu kodu.
private bool ValidationFunction()
{
if (validation logic here)
{
return true;
}
else
{
return false;
}
}
Metoda walidacji może mieć dowolny zakres i powinna zwrócić true
, jeśli wartość jest prawidłowa, a false
w przeciwnym razie. Jeśli metoda zwraca false
lub zgłasza wyjątek, wartość jest traktowana jako nieprawidłowa i zgłaszany jest błąd.
W poniższym przykładzie kod zezwala tylko na wartości zerowe lub większe współrzędnych X i Y.
private bool ValidatePoint()
{
if ((_x >= 0) && (_y >= 0))
{
return true;
}
else
{
return false;
}
}
Ograniczenia metody weryfikacji
Serwer wywołuje metodę weryfikacji, gdy serwer wykonuje konwersje, a nie podczas wstawiania danych przez ustawienie poszczególnych właściwości lub wstawienie danych przy użyciu instrukcji Transact-SQL INSERT
.
Należy jawnie wywołać metodę weryfikacji z metod ustawiających właściwości i metodę Parse
, jeśli chcesz, aby metoda walidacji wykonywana we wszystkich sytuacjach. Nie jest to wymagane, a w niektórych przypadkach nawet nie jest pożądane.
Przykład weryfikacji analizy
Aby upewnić się, że metoda ValidatePoint
jest wywoływana w klasie Point
, należy wywołać ją z metody Parse
i z procedur właściwości, które ustawiają wartości współrzędnych X i Y. Poniższy fragment kodu pokazuje, jak wywołać metodę weryfikacji ValidatePoint
z funkcji Parse
.
[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;
}
Przykład weryfikacji właściwości
Poniższy fragment kodu pokazuje, jak wywołać metodę weryfikacji ValidatePoint
z procedur właściwości, które ustawiają współrzędne X i Y.
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.");
}
}
}
Metody UDT kodu
Podczas kodowania metod UDT należy rozważyć, czy używany algorytm może ulec zmianie w czasie. Jeśli tak, warto rozważyć utworzenie oddzielnej klasy dla metod używanych przez użytkownika. Jeśli algorytm ulegnie zmianie, możesz ponownie skompilować klasę przy użyciu nowego kodu i załadować zestaw do programu SQL Server bez wpływu na udT. W wielu przypadkach można ponownie załadować trasy zdefiniowane przez użytkownika przy użyciu instrukcji Transact-SQL ALTER ASSEMBLY
, ale może to spowodować problemy z istniejącymi danymi. Na przykład funkcja Currency
UDT dołączona do przykładowej bazy danych AdventureWorks2022
używa funkcji ConvertCurrency
do konwertowania wartości walutowych, które są implementowane w oddzielnej klasie. Możliwe, że algorytmy konwersji mogą ulec zmianie w nieprzewidywalny sposób w przyszłości lub że mogą być wymagane nowe funkcje. Oddzielenie funkcji ConvertCurrency
od implementacji Currency
UDT zapewnia większą elastyczność podczas planowania przyszłych zmian.
Przykład
Klasa Point
zawiera trzy proste metody obliczania odległości: Distance
, DistanceFrom
i DistanceFromXY
. Każdy zwraca double
oblicza odległość od Point
do zera, odległość od określonego punktu do Point
oraz odległość od określonych współrzędnych X i Y do Point
.
Distance
i DistanceFrom
każde wywołanie DistanceFromXY
, i pokazuje, jak używać różnych argumentów dla każdej metody.
// 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));
}
Używanie atrybutów SqlMethod
Klasa Microsoft.SqlServer.Server.SqlMethodAttribute
udostępnia atrybuty niestandardowe, których można użyć do oznaczania definicji metod w celu określenia determinizmu, zachowania wywołania o wartości null i określenia, czy metoda jest mutatorem. Przyjmuje się wartości domyślne dla tych właściwości, a atrybut niestandardowy jest używany tylko wtedy, gdy wymagana jest wartość nie domyślna.
Nuta
Klasa SqlMethodAttribute
dziedziczy z klasy SqlFunctionAttribute
, dlatego SqlMethodAttribute
dziedziczy pola FillRowMethodName
i TableDefinition
z SqlFunctionAttribute
. Oznacza to, że można napisać metodę o wartości tabeli, która nie jest taka sama. Metoda kompiluje i wdraża zestaw, ale w czasie wykonywania jest zgłaszany błąd dotyczący IEnumerable
zwracanego typu z następującym komunikatem: "Metoda, właściwość lub pole <name>
w klasie <class>
w <assembly>
zestawu ma nieprawidłowy typ zwracany".
W poniższej tabeli opisano niektóre z odpowiednich właściwości Microsoft.SqlServer.Server.SqlMethodAttribute
, które mogą być używane w metodach UDT, i wymieniono ich wartości domyślne.
Własność | Opis |
---|---|
DataAccess |
Wskazuje, czy funkcja obejmuje dostęp do danych użytkownika przechowywanych w lokalnym wystąpieniu programu SQL Server. Wartość domyślna to DataAccessKind.None . |
IsDeterministic |
Wskazuje, czy funkcja generuje te same wartości wyjściowe, biorąc pod uwagę te same wartości wejściowe i ten sam stan bazy danych. Wartość domyślna to false . |
IsMutator |
Wskazuje, czy metoda powoduje zmianę stanu w wystąpieniu UDT. Wartość domyślna to false . |
IsPrecise |
Wskazuje, czy funkcja obejmuje nieprecyzyjne obliczenia, takie jak operacje zmiennoprzecinkowe. Wartość domyślna to false . |
OnNullCall |
Wskazuje, czy metoda jest wywoływana, gdy określono argumenty wejściowe odwołania o wartości null. Wartość domyślna to true . |
Przykład
Właściwość Microsoft.SqlServer.Server.SqlMethodAttribute.IsMutator
umożliwia oznaczenie metody, która umożliwia zmianę stanu wystąpienia funkcji UDT. Transact-SQL nie zezwala na ustawienie dwóch właściwości UDT w klauzuli SET
jednej instrukcji UPDATE
. Można jednak mieć metodę oznaczoną jako mutator, który zmienia dwa elementy członkowskie.
Nuta
Metody Mutatora nie są dozwolone w zapytaniach. Mogą być wywoływane tylko w instrukcjach przypisania lub instrukcjach modyfikacji danych. Jeśli metoda oznaczona jako mutator nie zwraca void
(lub nie jest Sub
w Visual Basic), CREATE TYPE
kończy się niepowodzeniem z powodu błędu.
W poniższej instrukcji przyjęto założenie istnienia Triangles
UDT, która ma metodę Rotate
. Następująca instrukcja Transact-SQL update wywołuje metodę Rotate
:
UPDATE Triangles
SET t.RotateY(0.6)
WHERE id = 5;
Metoda Rotate
jest ozdobiona ustawieniem atrybutu SqlMethod
IsMutator
w celu true
, aby program SQL Server mógł oznaczyć metodę jako metodę mutatora. Kod ustawia również OnNullCall
na false
, który wskazuje serwer, że metoda zwraca odwołanie o wartości null (Nothing
w Visual Basic), jeśli którykolwiek z parametrów wejściowych są odwołaniami o wartości null.
[SqlMethod(IsMutator = true, OnNullCall = false)]
public void Rotate(double anglex, double angley, double anglez)
{
RotateX(anglex);
RotateY(angley);
RotateZ(anglez);
}
Implementowanie funkcji UDT przy użyciu formatu zdefiniowanego przez użytkownika
Podczas implementowania funkcji UDT przy użyciu formatu zdefiniowanego przez użytkownika należy zaimplementować metody Read
i Write
, które implementują interfejs Microsoft.SqlServer.Server.IBinarySerialize
w celu obsługi serializacji i deserializacji danych UDT. Należy również określić właściwość MaxByteSize
Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute
.
Funkcja UDT waluty
Zestaw Currency
UDT jest dołączony do przykładów CLR, które można zainstalować za pomocą programu SQL Server.
Currency
UDT obsługuje obsługę kwot pieniędzy w systemie pieniężnym określonej kultury. Musisz zdefiniować dwa pola: string
dla CultureInfo
, który określa, kto wystawił walutę (na przykładen-us
, i decimal
dla CurrencyValue
, kwotę pieniędzy.
Mimo że nie jest używany przez serwer do przeprowadzania porównań, Currency
UDT implementuje interfejs System.IComparable
, który uwidacznia jedną metodę, System.IComparable.CompareTo
. Jest to używane po stronie klienta w sytuacjach, w których pożądane jest dokładne porównywanie lub zamawianie wartości waluty w kulturach.
Kod uruchomiony w środowisku CLR porównuje kulturę oddzielnie od wartości waluty. W przypadku kodu Transact-SQL następujące akcje określają porównanie:
Ustaw atrybut
IsByteOrdered
na wartość true, który informuje program SQL Server o użyciu utrwalonej reprezentacji binarnej na dysku na potrzeby porównań.Użyj metody
Write
dlaCurrency
UDT, aby określić, jak udT jest utrwalone na dysku i w jaki sposób wartości UDT są porównywane i uporządkowane dla operacji Transact-SQL.Zapisz
Currency
udT przy użyciu następującego formatu binarnego:Zapisz kulturę jako ciąg zakodowany w formacie UTF-16 dla bajtów 0–19 z dopełnianiem po prawej stronie z znakami null.
Użyj bajtów 20 i nowszych, aby zawierać wartość dziesiętną waluty.
Celem wypełnienia jest zapewnienie, że kultura jest całkowicie oddzielona od wartości waluty, tak aby w przypadku porównania jednego z nich w kodzie Transact-SQL, bajty kulturowe są porównywane z bajtami kultury, a wartości bajtów waluty są porównywane z wartościami bajtów waluty.
Atrybuty waluty
Currency
udT jest definiowany z następującymi atrybutami.
[Serializable]
[SqlUserDefinedType(Format.UserDefined,
IsByteOrdered = true, MaxByteSize = 32)]
[CLSCompliant(false)]
public struct Currency : INullable, IComparable, IBinarySerialize
{ ... }
Tworzenie metod odczytu i zapisu za pomocą metody ibinaryserialize
Po wybraniu formatu serializacji UserDefined
należy również zaimplementować interfejs IBinarySerialize
i utworzyć własne metody Read
i Write
. Poniższe procedury z Currency
UDT używają System.IO.BinaryReader
i System.IO.BinaryWriter
do odczytu i zapisu do 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 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();
}