Поделиться через


Несоответствия типов SQL-CLR

LINQ to SQL автоматизирует большую часть перевода между объектной моделью и SQL Server. Однако существуют ситуации, препятствующие точному преобразованию. Эти основные несоответствия между типами среды CLR и типами баз данных SQL Server перечислены в следующих разделах. Дополнительные сведения о сопоставлениях типов и переводе функций см. в статье "Сопоставление типов SQL-CLR" и "Типы данных и функции".

Типы данных

Преобразование между CLR и SQL Server происходит в тот момент, когда запрос направляется в базу данных, а также когда результаты отправляются обратно в модель объектов. Например, в следующем запросе Transact-SQL необходимы два преобразования значений:

Select DateOfBirth From Customer Where CustomerId = @id

Перед тем как запрос можно будет выполнить в SQL Server, должно быть указано значение параметра Transact-SQL. В этом примере значение параметра id должно быть сначала преобразовано из типа CLR System.Int32 в тип SQL Server INT, чтобы база данных могла понять, каково это значение. Затем, чтобы получить результат, столбец SQL Server DateOfBirth должен быть преобразован из типа SQL Server DATETIME в тип CLR System.DateTime, чтобы его можно было использовать в модели объектов. В этом примере типы модели объектов CLR естественным образом сопоставляются с типами базы данных SQL Server. Но так происходит не всегда.

Отсутствие аналогов

Следующие типы не имеют соответствующих аналогов.

  • Несоответствия в пространстве имен CLR System.

    • Целые числа без знака. Эти типы обычно сопоставляются со своими более крупными аналогами со знаками, чтобы избежать переполнения. На основе значения литералы могут быть преобразованы в числа со знаками такого же или меньшего размера.

    • Boolean. Эти типы могут быть сопоставлены битам, большим числам или строкам. Литерал может быть сопоставлен выражению, имеющему такое же значение (например, 1=1 в SQL для True в CLS).

    • TimeSpan. Этот тип представляет различие между двумя значениями DateTime и не соответствует типу timestamp SQL Server. В некоторых случаях тип CLR System.TimeSpan может также сопоставляться с типом TIME SQL Server. Назначение типа SQL Server TIME состоит только в том, чтобы представлять положительные значения менее суток. Диапазон типа CLR TimeSpan значительно шире.

    Примечание.

    Типы платформа .NET Framework sql Server не System.Data.SqlTypes включены в это сравнение.

  • Несоответствия в SQL Server.

    • Фиксированные типы символов длины. Transact-SQL различает категории Юникода и не Юникода и имеет три разных типа в каждой категории: фиксированная длина, переменная длина ncharnvarchar//varcharcharи более крупный размер.ntext/text Символьные типы фиксированной длины могут быть сопоставлены с типом CLR System.Char для получения символов, однако они не совсем соответствуют такому же типу в преобразованиях и поведении.

    • Бит. Хотя домен bit имеет то же число значений, что и Nullable<Boolean>, это два различных типа. Bit принимает значения 1 и 0 вместо true/falseне может использоваться в качестве эквивалента логическим выражениям.

    • Timestamp. В отличие от типа CLR System.TimeSpan тип SQL Server TIMESTAMP представляет созданное базой данных 8-разрядное число, уникальное для каждого обновления и не основанное на различии между значениями DateTime.

    • Деньги и SmallMoney. Эти типы могут быть сопоставлены типу Decimal, однако являются, по существу, разными типами и рассматриваются как таковые серверными функциями и преобразованиями.

Множественные сопоставления

Существует множество типов данных SQL Server, которые могут быть сопоставлены одному или нескольким типам данных CLR. С другой стороны, имеется множество типов CLR, которые могут быть сопоставлены с одним или несколькими типами SQL Server. Хотя сопоставление может быть поддержано средствами LINQ to SQL, это не значит, что два сопоставляемых типа, относящихся к CLR и SQL Server, полностью соответствуют друг другу по точности, диапазону и семантике. Некоторые сопоставления могут включать различия по любому из упомянутых измерений или по всем измерениям. Дополнительные сведения об этих потенциальных различиях можно найти в различных возможностях сопоставления в сопоставлении типов SQL-CLR.

Определяемые пользователем типы

Определяемые пользователем типы CLR разработаны, чтобы помочь устранить разрыв в системе типов. Однако с их помощью выявляются интересные моменты, касающиеся управления версиями типа. Изменение версии в клиенте может не иметь соответствующего изменения типа, хранящегося на сервере базы данных. Любое такое изменение приводит к несоответствию другого типа, при котором может отсутствовать соответствие семантик типа и может стать очевидным пропуск версии. Дальнейшие сложности возникают при оптимизации иерархий наследования в последующих версиях.

Семантики выражений

Кроме парного несоответствия между типами CLR и базы данных, несоответствие осложняется и выражениями. Необходимо учесть несоответствия в семантиках операторов, функций, в неявных преобразованиях типов, а также правила приоритета.

В следующих подразделах показано несоответствие между внешне схожими выражениями. Можно создать выражения SQL, которые семантически эквивалентны заданному выражению CLR. Однако не совсем ясно, являются ли семантические различия между внешне схожими выражениями очевидными для пользователя CLR и нужны ли изменения, необходимые для семантической равнозначности. Это является особенно важным вопросом при вычислении выражения для набора значений. Видимость различий может зависеть от данных и может быть труднораспознаваемой при кодировании и отладке.

Семантика NULL

Выражения SQL предоставляют логику с тремя значениями для логических выражений. Возможен следующий результат: true, false или NULL. С другой стороны, CLR указывает логический результат с двумя значениями для сравнений, использующих значения NULL. Рассмотрим следующий код:

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'.)

Схожая проблема возникает, исходя из предположения результатов с двумя значениями.

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.

В предыдущем случае можно получить эквивалентное поведение при создании SQL, однако преобразование может неточно отразить ваше намерение.

LINQ to SQL не накладывает семантику сравнения C# null или Visual Basic nothing на SQL. Операторы сравнения синтаксически преобразуются в эквивалентные команды SQL. Семантики отражают семантики SQL в соответствии с параметрами сервера или подключения. В заданных по умолчанию параметрах SQL два значения NULL считаются неравными (хотя, чтобы изменить семантики, можно изменить эти параметры). Независимо от того, LINQ to SQL не учитывает параметры сервера в переводе запросов.

Сравнение с литералом null (nothing) преобразуется в соответствующую версию SQL (is null или is not null).

Значение null (nothing) в сортировке определяется SQL Server; LINQ to SQL не изменяет параметры сортировки.

Преобразование и повышение типов

SQL поддерживает широкий набор неявных преобразований в выражениях. Для схожих выражений в C# потребуется явное приведение Например:

  • Типы Nvarchar и DateTime можно сравнить в SQL без явных приведений; C# требует явного преобразования.

  • Decimal неявно преобразуется в DateTime в SQL. Язык C# не допускает неявных преобразований.

Аналогично: приоритеты типов в Transact-SQL отличаются от приоритетов типов C# из-за отличий в базовом наборе типов. В действительности же явная связь поднабора/расширенного набора между списками приоритетов отсутствует. Так, при сравнении типов nvarchar с varchar выполняется неявное преобразование выражения типа varchar в nvarchar. Среда CLR не предоставляет эквивалентного повышения.

В простых случаях различия приводят к выражениям CLR с приведениями, которые избыточны для соответствующего выражения SQL. Более важно то, что промежуточные результаты выражения SQL могут быть неявно повышены до типа, не имеющего точного аналога в языке C#, и наоборот. В целом тестирование, отладка и проверка таких выражений является дополнительной значительной нагрузкой для пользователя.

Параметры сортировки

Transact-SQL поддерживает явные параметры сортировки в качестве заметок к типам символьных строк. Эти параметры сортировки определяют допустимость определенных сравнений. Например, сравнение двух столбцов с различными явными параметры сортировки недопустимо. Использование более простого строкового типа CTS не вызывает подобных ошибок. Рассмотрим следующий пример:

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.

В действительности подклауз параметров сортировки создает ограниченный тип , который не является подстановимым.

Аналогично, в системах типов могут применяться самые разные порядки сортировки. Это различие влияет на сортировку результатов. Идентификатор Guid сортируется во всех 16 байтах в лексикографическом порядке (IComparable()), тогда как T-SQL сравнивает идентификаторы GUID в следующем порядке: node(10-15), clock-seq(8-9), time-high(6-7), time-mid(4-5), time-low(0-3). Это упорядочивание выполнялось в SQL 7.0, где идентификаторы GUID, созданные NT-системой, имели именно такой октетный порядок. Данный способ обеспечивал, что GUID, созданные на одном кластере узла, последовательно объединялись в соответствии с меткой времени. Он также использовался для создания индексов (вставки становились добавлениями вместо случайных операций ввода-вывода). Позднее в Windows порядок был зашифрован в связи с вопросами обеспечения конфиденциальности, однако SQL должен поддерживать совместимость. Обходной путь — использовать SqlGuid вместо Guidэтого.

Отличия функции и оператора

Операторы и функции, являющиеся по существу сравнимыми, имеют незначительно отличающиеся семантики. Например:

  • C# задает сокращенные семантики на основе лексического порядка операндов для логических операторов && и ||. С другой стороны, SQL предназначен для запросов на основе наборов и поэтому предоставляет оптимизатору больше возможностей при выборе порядка выполнения. Ниже приведены некоторые выводы.

    • Для семантически эквивалентного перевода потребуется "CASE ... WHEN ... THEN" конструкция в SQL, чтобы избежать переупорядочения выполнения операнда.

    • Свободный перевод операторам AND/OR может привести к непредвиденным ошибкам, если выражение C# полагается на оценку второго операнда на основе результата оценки первого операнда.

  • Round()функция имеет разные семантики в платформа .NET Framework и В T-SQL.

  • Начальный индекс для строк в CLR равен нулю, а в SQL - 1. Поэтому для каждой функции с индексом необходимо преобразование индекса.

  • В отличие от SQL, среда CLR поддерживает оператор остатка от деления (%) для чисел с плавающей запятой.

  • Оператор Like эффективно использует автоматические перегрузки на основе неявных преобразований. Несмотря на то, что оператор Like определен для действия в типах символьных строк, неявное преобразование из числовых типов или типов DateTime допускает использование нестроковых типов с Like. В CTS сравнимые неявные преобразования не существуют. Поэтому необходимы дополнительные перегрузки.

    Примечание.

    Это поведение оператора Like характерно только для языка C#; ключевое слово Like Visual Basic остается неизменным.

  • Переполнение всегда проверка в SQL, но оно должно быть явно указано в C# (не в Visual Basic), чтобы избежать обходного решения. Даны столбцы целых чисел C1, C2 и C3, если C1+C2 хранится в C3 (Обновление 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
  • SQL выполняет симметричное арифметическое округление при платформа .NET Framework использует округление банкира. Дополнительные сведения см. в статье 196652 базы знаний.

  • По умолчанию для распространенных языков символьно-строковые сравнения в SQL не зависят от регистра. В Visual Basic и C# сравнения выполняются с учетом регистра. Например, s == "Food" (s = "Food" в Visual Basic) и s == "Food" может давать разные результаты, если s это 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.
  • Семантики операторов или функций, примененных к аргументам символьных типов фиксированной длины в SQL, значительно отличаются от семантик таких же операторов/функций, примененных к строке System.String CLR. Этот вопрос также можно рассмотреть как распространение проблемы отсутствующего аналога, которая обсуждается в разделе о типах.

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

    Похожая проблема возникает при объединении строк.

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

В результате для выражений CLR могут потребоваться сложные преобразования, а для доступа к возможностям SQL могут быть необходимы дополнительные операторы и функции.

Приведение типа

В C# и SQL пользователи могут переопределить заданные по умолчанию семантики выражений за счет использования явных приведений типов (Cast и Convert). Однако предоставление этой возможности в рамках системы типа создает трудности. Приведение SQL, предоставляющее необходимые семантики, не может быть просто преобразовано в соответствующее приведение C#. С другой стороны, приведение C# нельзя напрямую преобразовать в эквивалентное приведение SQL в связи с несоответствием типов, отсутствием аналогов и наличием иерархий приоритетов различных типов. Приходится идти на компромисс между несовпадением системы типа и потерей эффективности выражения.

В других случаях приведение типа может и не потребоваться в домене для проверки выражения, однако оно может быть необходимо для гарантии правильного применения к выражению нестандартного сопоставления.

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

Проблемы с производительностью

Учет некоторых различий типов SQL Server-CLR может привести к снижению производительности при пересечении между системами типов СРЕДЫ CLR и SQL Server. Помимо этого, возможны следующие сценарии, приводящие к снижению производительности.

  • Принудительный порядок оценки логических операторов and и or.

  • Создание SQL-запроса для принудительного порядка оценки предикатов ограничивает возможности оптимизатора SQL.

  • Преобразования типов, представленные компилятором CLR или реализацией объектно-реляционного запроса, могут ограничить использование индекса.

    Например,

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

    Рассмотрим преобразование выражения (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.
    

Наряду с семантическими различиями при переходах между системами типов SQL Server и CLR важно учитывать изменение производительности. Для больших наборов данных подобные проблемы производительности могут определить, является ли приложение развертываемым.

См. также