次の方法で共有


SQL と CLR の型の不一致

LINQ to SQL では、オブジェクト モデルと SQL Server の間の変換の多くが自動化されます。 ただし、状況によっては正確な翻訳が妨げる場合があります。 共通言語ランタイム (CLR) 型と SQL Server データベース型の間のこれらのキーの不一致を次のセクションにまとめます。 特定の型マッピングと関数変換の詳細については、「 SQL-CLR 型マッピングデータ型と関数」を参照してください。

データ型

CLR と SQL Server の間の変換は、クエリがデータベースに送信され、結果がオブジェクト モデルに送り返されるときに発生します。 たとえば、次の Transact-SQL クエリでは、2 つの値の変換が必要です。

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] 。 これらの型は、ビットまたは大きい数値または文字列にマップできます。 リテラルは、同じ値に評価される式に対応付けることができます (たとえば、SQL の1=1 は CLS のTrue に)。

    • TimeSpan。 この型は、2 つの DateTime 値の違いを表し、SQL Server の timestamp には対応しません。 場合によっては、CLR System.TimeSpan が SQL Server TIME 型にマップされる場合もあります。 SQL Server TIME 型は、24 時間未満の正の値のみを表すことを目的としていました。 CLR TimeSpan には、はるかに大きな範囲があります。

    この比較では、 System.Data.SqlTypes の SQL Server 固有の .NET Framework 型は含まれません。

  • SQL Server での不一致:

    • 固定長文字型。 Transact-SQL Unicode カテゴリと Unicode 以外のカテゴリを区別し、各カテゴリには、固定長 nchar/char、可変長 nvarchar/varchar、および大きなサイズの ntext/textの 3 つの異なる型があります。 固定長文字型は、文字を取得するために CLR System.Char 型にマップできますが、実際には変換と動作で同じ型に対応していません。

    • ビットbit ドメインの値の数はNullable<Boolean>と同じですが、2 つの型は異なります。 Bitは、10trueではなく/値とfalse値を受け取り、ブール式と同等の値として使用することはできません。

    • タイムスタンプ。 CLR System.TimeSpan 型とは異なり、SQL Server TIMESTAMP 型は、更新ごとに一意であり、 DateTime 値の違いに基づいていない、データベースによって生成された 8 バイトの数値を表します。

    • MoneySmallMoney。 これらの型は Decimal にマップできますが、基本的に異なる型であり、サーバーベースの関数や変換によってそのように扱われます。

複数のマッピング

1 つ以上の CLR データ型にマップできる SQL Server データ型は多数あります。 また、1 つ以上の SQL Server 型にマップできる CLR 型も多数あります。 マッピングは LINQ to SQL でサポートされる場合がありますが、CLR と SQL Server の間でマップされる 2 つの型が、有効桁数、範囲、セマンティクスに完全に一致することを意味するものではありません。 一部のマッピングには、これらのディメンションの一部またはすべてに違いが含まれる場合があります。 さまざまなマッピングの可能性に対するこれらの潜在的な違いの詳細については、「 SQL-CLR 型マッピング」を参照してください。

ユーザー定義型

ユーザー定義 CLR 型は、型システムギャップを埋めるために設計されています。 ただし、型のバージョン管理に関する興味深い問題が発生します。 クライアントのバージョンの変更は、データベース サーバーに格納されている型の変更と一致しない可能性があります。 このような変更により、型セマンティクスが一致しない可能性があり、バージョンのギャップが表示される可能性がある別の型の不一致が発生します。 継承階層が連続するバージョンでリファクタリングされると、さらに複雑になります。

式のセマンティクス

CLR 型とデータベース型のペアごとの不一致に加えて、式によって不一致が複雑になります。 演算子セマンティクス、関数セマンティクス、暗黙的な型変換、および優先順位ルールの不一致を考慮する必要があります。

次のサブセクションは、明らかに類似した式の間の不一致を示しています。 特定の CLR 式と意味的に等価な SQL 式を生成できる場合があります。 ただし、明らかに類似した式間のセマンティックの違いが CLR ユーザーに明らかであるかどうか、したがって、セマンティック等価性に必要な変更が意図されているかどうかは明らかではありません。 これは、一連の値に対して式が評価される場合に特に重要な問題です。 違いの可視性はデータによって異なる場合があり、コーディングとデバッグ中に識別するのが難しい場合があります。

null セマンティクス

SQL 式は、ブール式に対して 3 つの値を持つロジックを提供します。 結果は true、false、または null にすることができます。 これに対し、CLR では、null 値を含む比較に対して 2 つの値を持つブール値の結果を指定します。 次のコードについて考えてみましょう。

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

同様の問題は、2 値の結果に関する前提で発生します。

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 では、SQL で C# null または Visual Basic nothing 比較セマンティクスは適用されません。 比較演算子は、構文的に SQL に相当するものに変換されます。 セマンティクスには、サーバーまたは接続の設定で定義されている SQL セマンティクスが反映されます。 SQL Server の既定の設定では、2 つの null 値が等しくないと見なされます (ただし、設定を変更してセマンティクスを変更することはできます)。 ただし、LINQ to SQL では、クエリ変換でのサーバー設定は考慮されません。

リテラル null (nothing) との比較は、適切な SQL バージョン (is null または is not null) に変換されます。

照合順序の null (nothing) の値は SQL Server によって定義されます。LINQ to SQL では照合順序は変更されません。

型変換と昇格

SQL では、式の暗黙的な変換の豊富なセットがサポートされています。 C# の同様の表現では、明示的なキャストが必要となります。 例えば次が挙げられます。

  • Nvarchar SQL では、明示的なキャストを使用せずに、 DateTime 型を比較できます。C# には明示的な変換が必要です。

  • Decimal は、SQL で暗黙的に DateTime に変換されます。 C# では、暗黙的な変換は許可されません。

同様に、Transact-SQL の型の優先順位は、基になる型のセットが異なるため、C# の型の優先順位とは異なります。 実際、優先順位リスト間に明確なサブセット/スーパーセット関係はありません。 たとえば、 nvarcharvarchar を比較すると、 varchar 式が暗黙的に nvarchar変換されます。 CLR は同等の昇格を提供しません。

単純なケースでは、これらの違いにより、対応する SQL 式に対してキャストを含む CLR 式が冗長になります。 さらに重要なのは、SQL 式の中間結果が、C# で正確に対応しない型に暗黙的に昇格される可能性があります。また、その逆も同様です。 全体的に、このような式のテスト、デバッグ、検証は、ユーザーに大きな負担を与えます。

コレーション

Transact-SQL では、明示的な照合順序が文字列型の注釈としてサポートされます。 これらの照合順序は、特定の比較の有効性を決定します。 たとえば、明示的な照合順序が異なる 2 つの列を比較すると、エラーになります。 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 は、辞書順 (IComparable()) で 16 バイトすべてで並べ替えられます。一方、T-SQL では、node(10-15)、clock-seq(8-9)、time-high(6-7)、time-mid(4-5)、time-low(0-3) の順序で GUID が比較されます。 この順序付けは、NT で生成された GUID にこのようなオクテット順序があったときに SQL 7.0 で行われました。 このアプローチにより、同じノード クラスターで生成された GUID が、タイムスタンプに従って順番に結合されることが保証されました。 このアプローチは、インデックスを作成する場合にも役立ちました (挿入はランダム IO の代わりに追加されます)。 後でWindows上でプライバシー上の問題により順序が変更されましたが、SQLは互換性を維持する必要があります。 回避策は、SqlGuidではなくGuidを使用することです。

演算子と関数の違い

実質的に同等の演算子と関数には、微妙に異なるセマンティクスがあります。 例えば次が挙げられます。

  • C# では、論理演算子の &&||のオペランドの構文の順序に基づいて、短絡セマンティクスを指定します。 一方、SQL はセット ベースのクエリを対象としているため、オプティマイザーが実行順序を決定する自由度が高くなります。 次のような影響があります。

    • セマンティクスが等価になるように変換するには、SQL の "CASEWHENTHENオペランドの実行の並べ替えを回避するための SQL のコンストラクト。

    • AND / OR演算子への緩やかな変換では、C# 式が最初のオペランドの評価の結果に基づく 2 番目のオペランドの評価に依存している場合、予期しないエラーが発生する可能性があります。

  • Round() 関数には、.NET Framework と T-SQL で異なるセマンティクスがあります。

  • 文字列の開始インデックスは CLR では 0 ですが、SQL では 1 です。 そのため、インデックスを持つ関数にはインデックス変換が必要です。

  • CLR では、浮動小数点数に剰余演算子 (%) がサポートされていますが、SQL ではサポートされていません。

  • Like演算子は、暗黙的な変換に基づいて自動オーバーロードを効果的に取得します。 Like演算子は文字列型を操作するように定義されていますが、数値型またはDateTime型からの暗黙的な変換では、文字列以外の型もLikeで使用できます。 CTS では、同等の暗黙的な変換は存在しません。 そのため、追加のオーバーロードが必要です。

    この Like 演算子の動作は C# にのみ適用されます。Visual Basic Like キーワードは変更されません。

  • オーバーフローは常に SQL でチェックされますが、ラップアラウンドを回避するには(Visual Basic ではなく) C# で明示的に指定する必要があります。 たとえば、整数の列 C1、C2、および C3 があり、C1+C2 が 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
  • SQL では対称的な算術型丸めが実行されますが、.NET Framework では銀行型丸めが使用されます。 詳細については、ナレッジ ベースの記事196652を参照してください。

  • 既定で、共通ロケールの SQL で文字や文字列を比較する場合に大文字と小文字は区別されません。 Visual Basic と C# では、大文字と小文字は区別されます。 たとえば、s == "Food"s = "Food"の場合、s == "Food" (Visual Basic では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 の固定長文字型引数に適用される演算子/関数のセマンティクスは、CLR System.Stringに適用されるのと同じ演算子/関数と大きく異なります。 これは、型に関するセクションで説明されている不足している対応する問題の拡張と見なすこともできます。

    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 では、明示的な型キャスト (CastConvert) を使用して、式の既定のセマンティクスをオーバーライドできます。 ただし、型システム境界を越えてこの機能を公開すると、ジレンマが起きます。 目的のセマンティクスを提供する 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 コンパイラによって導入された場合でも、Object-Relational クエリ実装によって導入された場合でも、インデックスの使用を抑制する可能性があります。

    たとえば、

    -- 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 型システムをまたぐ場合のパフォーマンスへの影響を考慮することが重要です。 大規模なデータ セットの場合、このようなパフォーマンスの問題によって、アプリケーションがデプロイ可能かどうかを判断できます。

こちらも参照ください