Dela via


Frågesemantik för nullvärden

Inledning

SQL-databaser fungerar på 3-värderad logik (true, false, null) när du utför jämförelser, i motsats till den booleska logiken i C#. När DU översätter LINQ-frågor till SQL försöker EF Core kompensera för skillnaden genom att införa ytterligare null-kontroller för vissa element i frågan. För att illustrera detta ska vi definiera följande entitet:

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; }
}

och utfärda flera frågor:

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);

De två första frågorna ger enkla jämförelser. I den första frågan är båda kolumnerna inte nullbara, så null-kontroller behövs inte. I den andra frågan kan NullableInt innehålla null, men Id är inte nullable; att jämföra null med icke-null ger null som ett resultat, vilket skulle filtreras bort av WHERE-operationen. Därför behövs inga ytterligare villkor heller.

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]

Den tredje frågan introducerar en null-kontroll. När NullableInt är null ger jämförelsen Id <> NullableIntnull, vilket skulle filtreras bort av WHERE-åtgärden. Men ur det booleska logikperspektivet bör det här fallet returneras som en del av resultatet. Ef Core lägger därför till den nödvändiga kontrollen för att säkerställa detta.

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

Frågor fyra och fem visar mönstret när båda kolumnerna är nullbara. Det är värt att notera att åtgärden <> ger en mer komplicerad (och potentiellt långsammare) fråga än åtgärden == .

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)

Behandling av nullbara värden i funktioner

Många funktioner i SQL kan bara returnera ett null resultat om några av deras argument är null. EF Core drar nytta av detta för att skapa effektivare frågor. Frågan nedan illustrerar optimeringen:

var query = context.Entities.Where(e => e.String1.Substring(0, e.String2.Length) == null);

Den genererade SQL:en är följande (vi behöver inte utvärdera funktionen eftersom den SUBSTRING bara är null när något av argumenten är 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

Optimeringen kan också användas för användardefinierade funktioner. Mer information finns på sidan för användardefinierad funktionsmappning .

Skriva högpresterande frågor

  • Det är enklare och snabbare att jämföra icke-nullbara kolumner än att jämföra nullbara kolumner. Överväg att markera kolumner som icke-nullbara när det är möjligt.

  • Det är enklare och snabbare att söka efter likhet (==) än att söka efter icke-likhet (!=), eftersom frågan inte behöver skilja mellan null och false resultat. Använd likhetsjämförelse när det är möjligt. Men att bara negera == jämförelsen är i praktiken detsamma som !=, så det resulterar inte i prestandaförbättringar.

  • I vissa fall är det möjligt att förenkla en komplex jämförelse genom att filtrera bort null värden från en kolumn explicit , till exempel när inga null värden finns eller dessa värden inte är relevanta i resultatet. Tänk på följande exempel:

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));

Dessa frågor skapar följande 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)))

I den andra frågan null filtreras resultaten uttryckligen bort från String1 kolumnen. EF Core kan på ett säkert sätt behandla String1 kolumnen som icke-nullbar under jämförelsen, vilket resulterar i en enklare fråga.

Använda relations-null-semantik

Det går att inaktivera nolljämförelsekompensationen och använda relations-null-semantik direkt. Detta kan göras genom att anropa UseRelationalNulls(true) metoden på alternativverktyget inuti OnConfiguring metoden:

new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();

Varning

När du använder relations-null-semantik har dina LINQ-frågor inte längre samma innebörd som de gör i C#, och kan ge andra resultat än förväntat. Var försiktig när du använder det här läget.