Udostępnij za pośrednictwem


Niezgodność typu SQL CLR

LINQ to SQL automatyzuje znaczną część tłumaczenia między modelem obiektów a programem SQL Server. Niemniej jednak niektóre sytuacje uniemożliwiają dokładne tłumaczenie. Te niezgodności kluczy między typami środowiska uruchomieniowego języka wspólnego (CLR) i typami baz danych programu SQL Server są podsumowane w poniższych sekcjach. Więcej szczegółów na temat mapowań typów i tłumaczenia funkcji można znaleźć na stronie Mapowanie typów SQL-CLR i typy danych i funkcje.

Typy danych

Tłumaczenie między środowiskiem CLR i programem SQL Server występuje, gdy zapytanie jest wysyłane do bazy danych, a wyniki są wysyłane z powrotem do modelu obiektów. Na przykład następujące zapytanie Języka Transact-SQL wymaga dwóch konwersji wartości:

Select DateOfBirth From Customer Where CustomerId = @id

Przed wykonaniem zapytania w programie SQL Server należy określić wartość parametru Transact-SQL. W tym przykładzie wartość parametru id musi najpierw zostać przetłumaczona z typu CLR System.Int32 na typ programu SQL Server INT , aby baza danych mogła zrozumieć, jaka jest wartość. Następnie, aby pobrać wyniki, kolumna programu SQL Server musi zostać przetłumaczona z typu programu SQL Server DateOfBirthDATETIME na typ CLR System.DateTime do użycia w modelu obiektów. W tym przykładzie typy w modelu obiektów CLR i bazie danych programu SQL Server mają naturalne mapowania. Ale to nie zawsze jest tak.

Brak odpowiedników

Następujące typy nie mają rozsądnych odpowiedników.

  • Niezgodność w przestrzeni nazw CLR System :

    • Niepodpisane liczby całkowite. Te typy są zwykle mapowane na podpisane odpowiedniki o większym rozmiarze, aby uniknąć przepełnienia. Literały można przekonwertować na cyfrę ze znakiem o tym samym lub mniejszym rozmiarze na podstawie wartości.

    • Wartość logiczna. Te typy można mapować na bit lub większą liczbę lub ciąg. Literał można zamapować na wyrażenie, które oblicza tę samą wartość (na przykład 1=1 w języku SQL dla True w clS).

    • Przedział czasu. Ten typ reprezentuje różnicę między dwiema DateTime wartościami i nie odpowiada programowi timestamp SQL Server. ClR System.TimeSpan może również mapować typ programu SQL Server TIME w niektórych przypadkach. Typ programu SQL Server TIME był przeznaczony tylko do reprezentowania wartości dodatnich krótszych niż 24 godziny. CLR TimeSpan ma znacznie większy zakres.

    Uwaga

    Typy programu .NET Framework specyficzne dla programu System.Data.SqlTypes SQL Server nie są uwzględnione w tym porównaniu.

  • Niezgodność w programie SQL Server:

    • Typy znaków o stałej długości. Język Transact-SQL rozróżnia kategorie Unicode i inne niż Unicode oraz trzy odrębne typy w każdej kategorii: stałą długość, zmienną długość ncharnvarcharvarcharchar//i większy rozmiar.ntext/text Typy znaków o stałej długości można mapować na typ CLR System.Char na potrzeby pobierania znaków, ale tak naprawdę nie odpowiadają temu samemu typowi konwersji i zachowania.

    • Bit. Mimo że domena bit ma taką samą liczbę wartości jak Nullable<Boolean>, te dwa typy są różne. Bit przyjmuje wartości 1 i 0 zamiast true/false, i nie może być używany jako odpowiednik wyrażeń logicznych.

    • Sygnatura czasowa. W przeciwieństwie do typu CLR System.TimeSpan typ programu SQL Server TIMESTAMP reprezentuje liczbę 8 bajtów wygenerowaną przez bazę danych, która jest unikatowa dla każdej aktualizacji i nie jest oparta na różnicy między wartościami DateTime .

    • Pieniądze i SmallMoney. Te typy można mapować, Decimal ale są zasadniczo różne typy i są traktowane jako takie przez funkcje i konwersje oparte na serwerze.

Wiele mapowań

Istnieje wiele typów danych programu SQL Server, które można mapować na co najmniej jeden typ danych CLR. Istnieje również wiele typów CLR, które można mapować na co najmniej jeden typ programu SQL Server. Mimo że mapowanie może być obsługiwane przez LINQ to SQL, nie oznacza to, że dwa typy mapowane między CLR i SQL Server są idealnym dopasowaniem do precyzji, zakresu i semantyki. Niektóre mapowania mogą zawierać różnice w dowolnych lub wszystkich tych wymiarach. Szczegółowe informacje na temat tych potencjalnych różnic można znaleźć w sekcji Mapowanie typów SQL-CLR.

Typy zdefiniowane przez użytkownika

Typy CLR zdefiniowane przez użytkownika zostały zaprojektowane tak, aby ułatwić łączenie luk w systemie typów. Niemniej jednak występują interesujące problemy dotyczące przechowywania wersji typów. Zmiana wersji na kliencie może być niezgodna ze zmianą typu przechowywanego na serwerze bazy danych. Każda taka zmiana powoduje niezgodność innego typu, w którym semantyka typów może nie być zgodna, a luka wersji prawdopodobnie stanie się widoczna. Kolejne komplikacje występują, ponieważ hierarchie dziedziczenia są refaktoryzowane w kolejnych wersjach.

Semantyka wyrażeń

Oprócz niezgodności parowania między typami CLR i baz danych wyrażenia dodają złożoność do niezgodności. Należy rozważyć niezgodność semantyki operatorów, semantyki funkcji, niejawnej konwersji typów i reguł pierwszeństwa.

Poniższe podsekcje ilustrują niezgodność między najwyraźniej podobnymi wyrażeniami. Może być możliwe wygenerowanie wyrażeń SQL, które są semantycznie równoważne z danym wyrażeniem CLR. Nie jest jednak jasne, czy semantyczne różnice między najwyraźniej podobnymi wyrażeniami są widoczne dla użytkownika CLR, a zatem czy zmiany wymagane do równoważności semantycznej są zamierzone, czy nie. Jest to szczególnie krytyczny problem, gdy wyrażenie jest oceniane dla zestawu wartości. Widoczność różnicy może zależeć od danych i być trudna do zidentyfikowania podczas kodowania i debugowania.

Semantyka wartości Null

Wyrażenia SQL zapewniają trzywartą logikę dla wyrażeń logicznych. Wynik może mieć wartość true, false lub null. Z kolei CLR określa dwuwarty wynik logiczny dla porównań obejmujących wartości null. Spójrzmy na poniższy kod:

Nullable<int> i = null;
Nullable<int> j = null;
if (i == j)
{
    // This branch is executed.
}
Dim i? As Integer = Nothing
Dim j? As Integer = Nothing
If i = j Then
    '  This branch is executed.
End If
-- Assume col1 and col2 are integer columns with null values.
-- Assume that ANSI null behavior has not been explicitly
--  turned off.
Select …
From …
Where col1 = col2
-- Evaluates to null, not true and the corresponding row is not
--   selected.
-- To obtain matching behavior (i -> col1, j -> col2) change
--   the query to the following:
Select …
From …
Where
    col1 = col2
or (col1 is null and col2 is null)
-- (Visual Basic 'Nothing'.)

Podobny problem występuje z założeniem o dwóch wartościach wyników.

if ((i == j) || (i != j)) // Redundant condition.
{
    // ...
}
If (i = j) Or (i <> j) Then ' Redundant condition.
    ' ...
End If
-- Assume col1 and col2 are nullable columns.
-- Assume that ANSI null behavior has not been explicitly
--   turned off.
Select …
From …
Where
    col1 = col2
or col1 != col2
-- Visual Basic: col1 <> col2.

-- Excludes the case where the boolean expression evaluates
--   to null. Therefore the where clause does not always
--   evaluate to true.

W poprzednim przypadku można uzyskać równoważne zachowanie podczas generowania kodu SQL, ale tłumaczenie może nie odzwierciedlać dokładnie intencji.

LinQ to SQL nie nakłada semantyki porównania języka C# null ani Visual Basic nothing na język SQL. Operatory porównania są syntactycznie tłumaczone na ich odpowiedniki SQL. Semantyka odzwierciedla semantykę SQL zdefiniowaną przez ustawienia serwera lub połączenia. Dwie wartości null są uznawane za nierówne w domyślnych ustawieniach programu SQL Server (chociaż można zmienić ustawienia, aby zmienić semantyka). Niezależnie od tego, LINQ to SQL nie uwzględnia ustawień serwera w translacji zapytań.

Porównanie z literałem null (nothing) jest tłumaczone na odpowiednią wersję SQL (is null lub is not null).

Wartość (nothing) w sortowaniu null jest definiowana przez program SQL Server; LinQ to SQL nie zmienia sortowania.

Konwersja typów i podwyższanie poziomu

Język SQL obsługuje bogaty zestaw niejawnych konwersji w wyrażeniach. Podobne wyrażenia w języku C# wymagają jawnego rzutowania. Na przykład:

  • Nvarchar typy i DateTime można porównać w języku SQL bez żadnych jawnych rzutów; Język C# wymaga jawnej konwersji.

  • Decimal jest niejawnie konwertowany na DateTime w języku SQL. Język C# nie zezwala na niejawną konwersję.

Podobnie pierwszeństwo typu w języku Transact-SQL różni się od pierwszeństwa typu w języku C#, ponieważ podstawowy zestaw typów jest inny. W rzeczywistości nie ma jasnej relacji podzestawu/nadzbioru między listami pierwszeństwa. Na przykład porównanie elementu nvarchar z elementem powoduje varchar niejawną konwersję varchar wyrażenia na nvarchar. CLR nie zapewnia równoważnej promocji.

W prostych przypadkach te różnice powodują nadmiarowe wyrażenia CLR z rzutami dla odpowiedniego wyrażenia SQL. Co ważniejsze, wyniki pośrednie wyrażenia SQL mogą być niejawnie promowane do typu, który nie ma dokładnego odpowiednika w języku C#i na odwrót. Ogólnie rzecz biorąc, testowanie, debugowanie i walidacja takich wyrażeń zwiększa znaczne obciążenie użytkownika.

Sortowanie

Język Transact-SQL obsługuje jawne sortowania jako adnotacje do typów ciągów znaków. Te sortowania określają ważność niektórych porównań. Na przykład porównanie dwóch kolumn z różnymi jawnymi sortowaniami jest błędem. Użycie znacznie uproszczonego typu ciągu CTS nie powoduje takich błędów. Rozważmy następujący przykład:

create table T2 (
    Col1 nvarchar(10),
    Col2      nvarchar(10) collate Latin_general_ci_as
)
class C
{
string s1;       // Map to T2.Col1.
string s2;       // Map to T2.Col2.

    void Compare()
    {
        if (s1 == s2) // This is correct.
        {
            // ...
        }
    }
}
Class C
    Dim s1 As String    ' Map to T2.Col1.
    Dim s2 As String    ' Map to T2.Col2.
    Sub Compare()
        If s1 = s2 Then ' This is correct.
            ' ...
        End If
    End Sub
End Class
Select …
From …
Where Col1 = Col2
-- Error, collation conflict.

W efekcie podklasa sortowania tworzy typ ograniczony, który nie jest zastępowalny.

Podobnie kolejność sortowania może znacznie się różnić w różnych systemach typów. Ta różnica wpływa na sortowanie wyników. Guid jest sortowana na wszystkich 16 bajtach według porządku leksykograficznego (IComparable()), natomiast język T-SQL porównuje identyfikatory GUID w następującej kolejności: node(10-15), clock-seq(8-9), time-high(6-7), time-mid(4-5), time-low (0-3). To zamówienie zostało wykonane w programie SQL 7.0, gdy identyfikatory GUID wygenerowane przez nt miały taką kolejność oktetów. Podejście zapewniało, że identyfikatory GUID wygenerowane w tym samym klastrze węzłów połączyły się w kolejności sekwencyjnej zgodnie ze znacznikami czasu. Podejście było również przydatne w przypadku tworzenia indeksów (wstawki stają się dołączane zamiast losowych operacji we/wy). Kolejność została zakodowana później w systemie Windows ze względu na obawy dotyczące prywatności, ale program SQL musi zachować zgodność. Obejściem jest użycie SqlGuid zamiast Guid.

Różnice operatorów i funkcji

Operatory i funkcje, które są zasadniczo porównywalne, mają subtelnie różne semantyki. Na przykład:

  • Język C# określa semantykę zwarciową opartą na kolejności leksykalnej operandów dla operatorów && logicznych i ||. Z drugiej strony usługa SQL jest przeznaczona dla zapytań opartych na zestawie i w związku z tym zapewnia większą swobodę optymalizatora do decydowania o kolejności wykonywania. Niektóre implikacje obejmują następujące kwestie:

    • Semantycznie równoważne tłumaczenie wymagałoby "CASE ... WHEN ... THEN" konstrukcja w języku SQL, aby uniknąć zmiany kolejności wykonywania operandu.

    • Luźne tłumaczenie operatorów AND/OR może spowodować nieoczekiwane błędy, jeśli wyrażenie języka C# opiera się na ocenie drugiego operandu opartego na wyniku oceny pierwszego operandu.

  • Round() funkcja ma różne semantyki w programie .NET Framework i w języku T-SQL.

  • Początkowy indeks ciągów wynosi 0 w środowisku CLR, ale 1 w języku SQL. W związku z tym każda funkcja, która ma indeks, wymaga tłumaczenia indeksu.

  • ClR obsługuje operator modulus ('%') dla liczb zmiennoprzecinkowych, ale sql nie.

  • Operator Like skutecznie uzyskuje automatyczne przeciążenia na podstawie niejawnych konwersji. Like Mimo że operator jest zdefiniowany tak, aby działał na typach ciągów znaków, niejawna konwersja z typów liczbowych lub DateTime typów umożliwia używanie tych typów innych niż ciągiLike. W usłudze CTS porównywalne konwersje niejawne nie istnieją. W związku z tym potrzebne są dodatkowe przeciążenia.

    Uwaga

    To Like zachowanie operatora dotyczy tylko języka C#; słowo kluczowe Visual Basic Like pozostaje niezmienione.

  • Funkcja Overflow jest zawsze ewidencjona w języku SQL, ale musi być jawnie określona w języku C# (nie w Visual Basic), aby uniknąć zawijania. Podane kolumny całkowite C1, C2 i C3, jeśli C1+C2 są przechowywane w C3 (Update T Set C3 = C1 + C2).

    create table T3 (
        Col1      integer,
        Col2      integer
    )
    insert into T3 (col1, col2) values (2147483647, 5)
    -- Valid values: max integer value and 5.
    select * from T3 where col1 + col2 < 0
    -- Produces arithmetic overflow error.
    
// C# overflow in absence of explicit checks.
int i = Int32.MaxValue;
int j = 5;
if (i+j < 0) Console.WriteLine("Overflow!");
// This code prints the overflow message.
' Does not apply.
' Visual Basic overflow in absence of implicit check
' (turn off overflow checks in compiler options)
Dim I As Integer = Int32.MaxValue
Dim j As Integer = 5
If I + j < 0 Then
    ' This code prints the overflow message.
    Console.WriteLine("Overflow!")
End If
  • Język SQL wykonuje zaokrąglanie arytmetyczne symetryczne, podczas gdy program .NET Framework używa zaokrąglania bankiera. Aby uzyskać więcej informacji, zobacz artykuł z bazy wiedzy 196652.

  • Domyślnie w przypadku typowych ustawień regionalnych porównania ciągów znaków są bez uwzględniania wielkości liter w języku SQL. W języku Visual Basic i W języku C#są uwzględniane wielkość liter. Na przykład s == "Food" (s = "Food" w Visual Basic) i s == "Food" może uzyskać różne wyniki, jeśli s ma wartość food.

    -- Assume default US-English locale (case insensitive).
    create table T4 (
        Col1      nvarchar (256)
    )
    insert into T4 values ('Food')
    insert into T4 values ('FOOD')
    select * from T4 where Col1 = 'food'
    -- Both the rows are returned because of case-insensitive matching.
    
// C# equivalent on collections of Strings in place of nvarchars.
String[] strings = { "food", "FOOD" };
foreach (String s in strings)
{
    if (s == "food")
    {
        Console.WriteLine(s);
    }
}
// Only "food" is returned.
' Visual Basic equivalent on collections of Strings in place of
' nvarchars.
Dim strings() As String = {"food", "FOOD"}
For Each s As String In strings
    If s = "food" Then
        Console.WriteLine(s)
    End If
Next
' Only "food" is returned.
  • Operatory/funkcje stosowane do argumentów typu znaków o stałej długości w języku SQL mają znacznie różne semantyki niż te same operatory/funkcje zastosowane do środowiska CLR System.String. Można to również zobaczyć jako rozszerzenie brakującego problemu odpowiednika omówionego w sekcji dotyczącej typów.

    create table T4 (
        Col1      nchar(4)
    )
    Insert into T5(Col1) values ('21');
    Insert into T5(Col1) values ('1021');
    Select * from T5 where Col1 like '%1'
    -- Only the second row with Col1 = '1021' is returned.
    -- Not the first row!
    
    // Assume Like(String, String) method.
    string s = ""; // map to T4.Col1
    if (System.Data.Linq.SqlClient.SqlMethods.Like(s, "%1"))
    {
        Console.WriteLine(s);
    }
    // Expected to return true for both "21" and "1021"
    
    ' Assume Like(String, String) method.
    Dim s As String    ' Map to T4.Col1.
    If s Like (System.Data.Linq.SqlClient.SqlMethods.Like(s, "%1")) Then
        Console.WriteLine(s)
    End If
    ' Expected to return true for both "21" and "1021".
    

    Podobny problem występuje w przypadku łączenia ciągów.

    create table T6 (
        Col1      nchar(4)
        Col2       nchar(4)
    )
    Insert into T6 values ('a', 'b');
    Select Col1+Col2 from T6
    -- Returns concatenation of padded strings "a   b   " and not "ab".
    

Podsumowując, dla wyrażeń CLR może być wymagane zawiłe tłumaczenie, a dodatkowe operatory/funkcje mogą być niezbędne do uwidocznienia funkcji SQL.

Rzutowanie typów

W języku C# i w języku SQL użytkownicy mogą zastąpić domyślną semantyka wyrażeń przy użyciu jawnych rzutów typu (Cast i Convert). Jednak uwidacznianie tej możliwości w granicach systemu typu stanowi dylemat. Rzutowanie SQL, które zapewnia żądaną semantyczną, nie można łatwo przetłumaczyć na odpowiednie rzutowanie w języku C#. Z drugiej strony rzutowanie w języku C# nie może być bezpośrednio tłumaczone na równoważne rzutowanie SQL z powodu niezgodności typów, brakujących odpowiedników i różnych hierarchii pierwszeństwa typu. Istnieje kompromis między uwidacznieniem niezgodności systemu typów a utratą znaczącej mocy wyrażenia.

W innych przypadkach rzutowanie typu może nie być potrzebne w żadnej domenie do weryfikacji wyrażenia, ale może być wymagane, aby upewnić się, że mapowanie nie domyślne jest poprawnie stosowane do wyrażenia.

-- Example from "Non-default Mapping" section extended
create table T5 (
    Col1      nvarchar(10),
    Col2      nvarchar(10)
)
Insert into T5(col1, col2) values ('3', '2');
class C
{
    int x;        // Map to T5.Col1.
    int y;        // Map to T5.Col2.

    void Casting()
    {
        // Intended predicate.
        if (x + y > 4)
        {
            // valid for the data above
        }
    }
}
Class C
    Dim x As Integer        ' Map to T5.Col1.
    Dim y As Integer        ' Map to T5.Col2.

    Sub Casting()
        ' Intended predicate.
        If (x + y) > 4 Then
            ' Valid for the data above.
        End If
    End Sub
End Class
Select *
From T5
Where Col1 + Col2 > 4
-- "Col1 + Col2" expr evaluates to '32'

Problemy z wydajnością

Uwzględnienie niektórych różnic typu SQL Server-CLR może spowodować spadek wydajności podczas przekraczania między systemami typów CLR i SQL Server. Przykłady scenariuszy wpływających na wydajność obejmują następujące elementy:

  • Wymuszona kolejność oceny dla operatorów logicznych i/lub

  • Generowanie kodu SQL w celu wymuszania kolejności oceny predykatu ogranicza zdolność optymalizatora SQL.

  • Konwersje typów, zarówno wprowadzone przez kompilator CLR, jak i implementację zapytań relacyjnych obiektów, mogą ograniczyć użycie indeksu.

    Przykład:

    -- Table DDL
    create table T5 (
        Col1      varchar(100)
    )
    
    class C5
    {
        string s;        // Map to T5.Col1.
    }
    
    Class C5
        Dim s As String ' Map to T5.Col1.
    End Class
    

    Rozważ tłumaczenie wyrażenia (s = SOME_STRING_CONSTANT).

    -- Corresponding part of SQL where clause
    Where …
    Col1 = SOME_STRING_CONSTANT
    -- This expression is of the form <varchar> = <nvarchar>.
    -- Hence SQL introduces a conversion from varchar to nvarchar,
    --   resulting in
    Where …
    Convert(nvarchar(100), Col1) = SOME_STRING_CONSTANT
    -- Cannot use the index for column Col1 for some implementations.
    

Oprócz różnic semantycznych należy wziąć pod uwagę wpływ na wydajność podczas przechodzenia między systemami typów SQL Server i CLR. W przypadku dużych zestawów danych takie problemy z wydajnością mogą określić, czy można wdrożyć aplikację.

Zobacz też