Condividi tramite


Semantica di Query Null

Introduzione

I database SQL operano sulla logica a 3 valori (true, false, null) quando si eseguono confronti, anziché la logica booleana di C#. Quando si convertono query LINQ in SQL, EF Core tenta di compensare la differenza introducendo controlli Null aggiuntivi per alcuni elementi della query. Per illustrare questo concetto, definire l'entità seguente:

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

ed eseguire diverse query:

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

Le prime due query producono confronti semplici. Nella prima query entrambe le colonne non sono nullabili, quindi non sono necessari controlli di nullità. Nella seconda query, NullableInt potrebbe contenere null, ma Id è non null; confrontare null a non-null produce null come risultato, che verrà filtrato dall'operazione WHERE. Quindi non sono necessari termini aggiuntivi.

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]

La terza query introduce un controllo Null. Quando NullableInt è null, il confronto Id <> NullableInt restituisce null, che verrebbe filtrato dall'operazione WHERE. Tuttavia, dal punto di vista della logica booleana questo caso deve essere restituito come parte del risultato. Di conseguenza, EF Core aggiunge il controllo necessario per garantirlo.

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

Le query quattro e cinque mostrano il modello quando entrambe le colonne sono annullabili. Vale la pena notare che l'operazione <> produce query più complesse (e potenzialmente più lente) rispetto all'operazione == .

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)

Trattamento dei valori nullable nelle funzioni

Molte funzioni in SQL possono restituire un null risultato solo se alcuni dei loro argomenti sono null. EF Core sfrutta questa funzionalità per produrre query più efficienti. La query seguente illustra l'ottimizzazione:

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

Il codice SQL generato è il seguente (non è necessario valutare la SUBSTRING funzione perché sarà null solo quando uno degli argomenti è 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

L'ottimizzazione può essere usata anche per le funzioni definite dall'utente. Per altri dettagli, vedere la pagina di mapping delle funzioni definite dall'utente .

Scrittura di query con prestazioni elevate

  • Il confronto tra colonne "non nullable" è più semplice e veloce rispetto al confronto tra colonne "nullable". Considerare di contrassegnare le colonne come non annullabili quando possibile.

  • Il controllo dell'uguaglianza (==) è più semplice e veloce rispetto al controllo della mancata uguaglianza (!=), perché la query non deve distinguere tra null e false risultato. Usare il confronto di uguaglianza quando possibile. Tuttavia, negare == il confronto è praticamente lo stesso !=, e quindi non migliora le prestazioni.

  • In alcuni casi, è possibile semplificare un confronto complesso filtrando esplicitamente i valori di una colonna, ad esempio quando non sono presenti valori null o questi valori non sono rilevanti nel risultato. Si consideri l'esempio seguente:

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

Queste query producono il codice SQL seguente:

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

Nella seconda query, i risultati null vengono esplicitamente filtrati dalla colonna String1. EF Core può considerare in modo sicuro la colonna come non annullabile durante il confronto String1, ottenendo una query più semplice.

Uso della semantica null relazionale

È possibile disabilitare la compensazione del confronto con null e usare direttamente la semantica dei null relazionale. A tale scopo, chiamare UseRelationalNulls(true) il metodo nel generatore di opzioni all'interno OnConfiguring del metodo :

new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();

Avviso

Quando si usa la semantica null relazionale, le query LINQ non hanno più lo stesso significato di quelle in C# e possono restituire risultati diversi rispetto al previsto. Prestare attenzione quando si usa questa modalità.