Sémantika dotazu null
Úvod
Databáze SQL pracují se 3hodnotovou logikou (true
, false
, null
) při porovnávání, a ne s logickou logikou jazyka C#. Při překladu dotazů LINQ do SQL se EF Core pokusí nahradit rozdíl zavedením dalších kontrol null pro některé prvky dotazu.
Pro ilustraci pojďme definovat následující entitu:
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; }
}
a vydávat několik dotazů:
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);
První dva dotazy vytvářejí jednoduchá porovnání. V prvním dotazu jsou oba sloupce bez hodnoty null, takže nejsou potřeba kontroly null. Ve druhém dotazu NullableInt
by mohl obsahovat null
hodnotu , ale Id
nemá hodnotu null; porovnávání null
s výnosy null
bez hodnoty null v důsledku toho, které by byly filtrovány podle WHERE
operace. Nejsou tedy potřeba žádné další termíny.
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]
Třetí dotaz zavádí kontrolu null. Pokud NullableInt
je null
výsledkem porovnání Id <> NullableInt
výnosy null
, které by se vyfiltrovaly podle WHERE
operace. Z logické logiky by se ale tento případ měl vrátit jako součást výsledku. Ef Core proto přidá potřebnou kontrolu, aby se zajistilo.
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
Dotazy čtyři a pět ukazují vzor, když oba sloupce mají hodnotu null. Stojí za zmínku <>
, že operace vytváří složitější (a potenciálně pomalejší) dotaz než ==
operace.
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)
Ošetření hodnot s možnou hodnotou null ve funkcích
Mnoho funkcí v SQL může vrátit null
pouze výsledek, pokud jsou některé z jejich argumentů null
. EF Core toho využívá k vytváření efektivnějších dotazů.
Následující dotaz znázorňuje optimalizaci:
var query = context.Entities.Where(e => e.String1.Substring(0, e.String2.Length) == null);
Vygenerovaný SQL je následující (nemusíme vyhodnotit SUBSTRING
funkci, protože bude mít hodnotu null pouze v případě, že některý z argumentů má hodnotu 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
Optimalizaci lze také použít pro uživatelem definované funkce. Další podrobnosti najdete na stránce mapování funkcí definovaných uživatelem.
Psaní výkonných dotazů
Porovnání sloupců, které nemají hodnotu null, je jednodušší a rychlejší než porovnání sloupců s možnou hodnotou null. Pokud je to možné, zvažte označení sloupců jako nenulové.
Kontrola rovnosti (
==
) je jednodušší a rychlejší než kontrola rovnosti (!=
), protože dotaz nemusí rozlišovat mezinull
afalse
výsledkem. Kdykoli je to možné, použijte porovnání rovnosti. Jednoduché porovnání negatingu==
je ale v podstatě stejné jako!=
, takže nezpůsobí zlepšení výkonu.V některýchpřípadechch
null
null
Podívejte se na následující příklad:
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));
Tyto dotazy vytvářejí následující 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)))
Ve druhém dotazu null
se výsledky explicitně odfiltrují ze String1
sloupce. EF Core může během porovnání bezpečně považovat String1
sloupec za nenulový, což vede k jednoduššímu dotazu.
Použití sémantiky relační hodnoty null
Kompenzaci porovnání null je možné zakázat a přímo použít sémantiku relační hodnoty null. To lze provést voláním UseRelationalNulls(true)
metody v tvůrci možností uvnitř OnConfiguring
metody:
new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();
Upozorňující
Při použití sémantiky s relační hodnotou null už dotazy LINQ nemají stejný význam jako v jazyce C# a můžou přinést jiné výsledky, než se čekalo. Při použití tohoto režimu buďte opatrní.