はじめに
SQL データベースは、C# のブールロジックではなく、比較を実行するときに 3 値ロジック (true、 false、 null) で動作します。 LINQ クエリを SQL に変換する場合、EF Core はクエリの一部の要素に対して追加の null チェックを導入することで、その違いを補正しようとします。
これを説明するために、次のエンティティを定義しましょう。
public class NullSemanticsEntity
{
public int Id { get; set; }
public int Int { get; set; }
public int? NullableInt { get; set; }
public string String1 { get; set; }
public string String2 { get; set; }
}
いくつかのクエリを発行します。
var query1 = context.Entities.Where(e => e.Id == e.Int);
var query2 = context.Entities.Where(e => e.Id == e.NullableInt);
var query3 = context.Entities.Where(e => e.Id != e.NullableInt);
var query4 = context.Entities.Where(e => e.String1 == e.String2);
var query5 = context.Entities.Where(e => e.String1 != e.String2);
最初の 2 つのクエリでは、単純な比較が生成されます。 最初のクエリでは、両方の列が null 非許容であるため、null チェックは必要ありません。 2 番目のクエリでは、 NullableInt に nullが含まれる可能性がありますが、 Id は null 非許容です。結果として、 null と null 以外の結果を比較すると null が生成され、 WHERE 操作によって除外されます。 そのため、追加の用語も必要ありません。
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[Id] = [e].[Int]
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[Id] = [e].[NullableInt]
3 番目のクエリでは、null チェックが導入されています。
NullableIntがnullのとき、比較Id <> NullableIntによりnullが得られます。これがWHERE操作で除去されます。 ただし、ブールロジックの観点からは、このケースは結果の一部として返される必要があります。 そのため、EF Core では、これを確認するために必要なチェックが追加されます。
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[Id] <> [e].[NullableInt]) OR [e].[NullableInt] IS NULL
クエリ 4 と 5 は、両方の列が null 許容の場合のパターンを示します。
<>操作では、==操作よりも複雑な (および低速になる可能性がある) クエリが生成される点に注目してください。
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[String1] = [e].[String2]) OR ([e].[String1] IS NULL AND [e].[String2] IS NULL)
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE (([e].[String1] <> [e].[String2]) OR ([e].[String1] IS NULL OR [e].[String2] IS NULL)) AND ([e].[String1] IS NOT NULL OR [e].[String2] IS NOT NULL)
関数での null 許容値の処理
SQL の多くの関数は、引数の一部がnullされている場合にのみnull結果を返すことができます。 EF Core はこれを利用して、より効率的なクエリを生成します。
次のクエリは、最適化を示しています。
var query = context.Entities.Where(e => e.String1.Substring(0, e.String2.Length) == null);
生成される SQL は次のとおりです ( SUBSTRING 関数を評価する必要はありません。これは、いずれかの引数が null の場合にのみ null になるためです)。
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[String1] IS NULL OR [e].[String2] IS NULL
最適化は、ユーザー定義関数にも使用できます。 詳細については、 ユーザー定義関数のマッピング に関するページを参照してください。
パフォーマンスの高いクエリの記述
NULL 非許容列の比較は、NULL 許容列を比較するよりも簡単で高速です。 可能な限り、列を null 非許容としてマークすることを検討してください。
等値 (
==) のチェックは、クエリで!=とnullの結果を区別する必要がないため、非等値 (false) をチェックするよりも簡単で高速です。 可能な限り等値比較を使用してください。 ただし、==比較を否定するだけでは、実質的に!=と同じであるため、パフォーマンスが向上することはありません。場合によっては、列から
null値を明示的に除外することで、複雑な比較を簡略化できます。たとえば、null値が存在しない場合や、これらの値が結果に関連しない場合などです。 次の例を確認してください。
var query1 = context.Entities.Where(e => e.String1 != e.String2 || e.String1.Length == e.String2.Length);
var query2 = context.Entities.Where(
e => e.String1 != null && e.String2 != null && (e.String1 != e.String2 || e.String1.Length == e.String2.Length));
これらのクエリにより、次の SQL が生成されます。
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ((([e].[String1] <> [e].[String2]) OR ([e].[String1] IS NULL OR [e].[String2] IS NULL)) AND ([e].[String1] IS NOT NULL OR [e].[String2] IS NOT NULL)) OR ((CAST(LEN([e].[String1]) AS int) = CAST(LEN([e].[String2]) AS int)) OR ([e].[String1] IS NULL AND [e].[String2] IS NULL))
SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[String1] IS NOT NULL AND [e].[String2] IS NOT NULL) AND (([e].[String1] <> [e].[String2]) OR (CAST(LEN([e].[String1]) AS int) = CAST(LEN([e].[String2]) AS int)))
2 番目のクエリでは、 null 結果は明示的 String1 列から除外されます。 EF Core では、比較中に String1 列を null 非許容として安全に処理できるため、クエリが簡単になります。
リレーショナル ヌル セマンティクスの使用
null 比較補正を無効にし、リレーショナル null セマンティクスを直接使用することができます。 これを行うには、 UseRelationalNulls(true) メソッド内のオプション ビルダーで OnConfiguring メソッドを呼び出します。
new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();
Warnung
リレーショナル null セマンティクスを使用する場合、LINQ クエリは C# と同じ意味を持たなくなり、予想とは異なる結果が得られる可能性があります。 このモードを使用する場合は注意が必要です。
.NET