Несоответствия типов SQL-CLR (LINQ to SQL)
Обновлен: November 2007
LINQ to SQL автоматизирует большую часть преобразований между моделью объектов и SQL Server. Однако существуют ситуации, препятствующие точному преобразованию. Эти основные несоответствия между типами среды CLR и типами баз данных SQL Server перечислены в следующих разделах. Дополнительные сведения о сопоставлении конкретных типов и о преобразовании функций см. в разделах Сопоставление типов SQL и CLR (LINQ to SQL) и Типы данных и функции (LINQ to SQL).
Типы данных
Преобразование между 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 Timespan может также сопоставляться с типом TIME SQL Server. Назначение типа SQL Server TIME состоит только в том, чтобы представлять положительные значения менее суток. Диапазон типа CLR Timespan значительно шире.
Примечание. В данное сопоставление не включаются связанные с SQL Server типы платформы .NET Framework в System.Data.SqlTypes.
Несоответствия в SQL Server.
Символьные типы фиксированной длины. В языке Transact-SQL различаются категории, представленные в Юникоде и в кодировке, отличной от Юникода, при этом в каждой категории выделяются три различающихся типа: типы фиксированной длины nchar/char, типы переменной длины nvarchar/varchar и типы более крупных размеров ntext/text. Символьные типы фиксированной длины могут быть сопоставлены с типом CLR System.Char для получения символов, однако они не совсем соответствуют такому же типу в преобразованиях и поведении.
Bit. Хотя домен bit имеет то же число значений, что и Nullable<Boolean>, это два различных типа. Bit принимает значения 1 и 0 (а не true/false) и не может быть использован в качестве эквивалента логических выражений.
Отметка времени. В отличие от типа CLR Timespan тип SQL Server TIMESTAMP представляет созданное базой данных 8-разрядное число, уникальное для каждого обновления и не основанное на различии между значениями DateTime.
Money и SmallMoney. Эти типы могут быть сопоставлены типу Decimal, однако являются, по существу, разными типами и рассматриваются как таковые серверными функциями и преобразованиями.
Множественные сопоставления
Существует множество типов данных SQL Server, которые могут быть сопоставлены одному или нескольким типам данных CLR. С другой стороны, имеется множество типов CLR, которые могут быть сопоставлены с одним или несколькими типами SQL Server. Хотя сопоставление может быть поддержано средствами LINQ to SQL, это не значит, что два сопоставляемых типа, относящихся к CLR и SQL Server, полностью соответствуют друг другу по точности, диапазону и семантике. Некоторые сопоставления могут включать различия по любому из упомянутых измерений или по всем измерениям. Дополнительные сведения об этих потенциальных различиях для различных вариантов сопоставлений см. в разделе Сопоставление типов SQL и CLR (LINQ to SQL).
Определяемые пользователем типы
Определяемые пользователем типы CLR разработаны, чтобы помочь устранить разрыв в системе типов. Однако с их помощью выявляются интересные моменты, касающиеся управления версиями типа. Изменение версии в клиенте может не иметь соответствующего изменения типа, хранящегося на сервере базы данных. Любое такое изменение приводит к несоответствию другого типа, при котором может отсутствовать соответствие семантик типа и может стать очевидным пропуск версии. Дальнейшие сложности возникают при оптимизации иерархий наследования в последующих версиях.
Семантики выражений
Кроме парного несоответствия между типами CLR и базы данных, несоответствие осложняется и выражениями. Необходимо учесть несоответствия в семантиках операторов, функций, в неявных преобразованиях типов, а также правила приоритета.
В следующих подразделах показано несоответствие между внешне схожими выражениями. Можно создать выражения SQL, которые семантически эквивалентны заданному выражению CLR. Однако не совсем ясно, являются ли семантические различия между внешне схожими выражениями очевидными для пользователя CLR и нужны ли изменения, необходимые для семантической равнозначности. Это является особенно важным вопросом при вычислении выражения для набора значений. Видимость различий может зависеть от данных и может быть труднораспознаваемой при кодировании и отладке.
Семантики значений NULL
Выражения SQL предоставляют логику с тремя значениями для логических выражений. Возможен следующий результат: true, false или NULL. С другой стороны, CLR указывает логический результат с двумя значениями для сравнений, использующих значения NULL. Рассмотрим следующий код.
Dim i? As Integer = Nothing
Dim j? As Integer = Nothing
If i = j Then
' This branch is executed.
End If
Nullable<int> i = null;
Nullable<int> j = null;
if (i == j)
{
// This branch is executed.
}
-- 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) Or (i <> j) Then ' Redundant condition.
' ...
End If
if ((i == j) || (i != j)) // Redundant condition.
{
// ...
}
-- 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
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
class C
{
string s1; // Map to T2.Col1.
string s2; // Map to T2.Col2.
void Compare()
{
if (s1 == s2) // This is correct.
{
// ...
}
}
}
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 предназначен для запросов на основе наборов и поэтому предоставляет оптимизатору больше возможностей при выборе порядка выполнения. Ниже приведены некоторые выводы.
Во избежание изменения порядка выполнения операнда для семантически эквивалентного преобразования в SQL потребуется конструкция "CASE … WHEN … THEN".
Свободное преобразование в операторы 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.
' 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
// 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.
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.
' 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.
// 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.
Семантики операторов или функций, примененных к аргументам символьных типов фиксированной длины в 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. 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".
// 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"
Похожая проблема возникает при объединении строк.
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
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
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
}
}
}
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 Dim s As String ' Map to T5.Col1. End Class
class C5 { string s; // Map to T5.Col1. }
Рассмотрим преобразование выражения (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 важно учитывать изменение производительности. Для больших наборов данных подобные проблемы производительности могут определить, является ли приложение развертываемым.