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 nullhodnotu , 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 mezi null a false 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 nullnull 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í.