Semantica Null query
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 nullable, quindi non sono necessari controlli Null. Nella seconda query NullableInt
potrebbe contenere null
, ma Id
non è nullable. Il confronto null
con i null
risultati non Null è quindi filtrato in base WHERE
all'operazione. 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
il null
confronto Id <> NullableInt
restituisce null
, che verrebbe filtrato in WHERE
base all'operazione. 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 assicurarsi che.
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 criterio quando entrambe le colonne sono nullable. 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 relativi 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 di colonne non nullable è più semplice e veloce rispetto al confronto di colonne nullable. Prendere in considerazione la possibilità di contrassegnare le colonne come non nullable quando possibile.
Il controllo dell'uguaglianza (
==
) è più semplice e veloce rispetto al controllo della mancata uguaglianza (!=
), perché la query non deve distinguere tranull
efalse
risultato. Usare il confronto di uguaglianza quando possibile. Tuttavia, la semplice negazione==
del confronto è in effetti uguale!=
a , quindi non comporta un miglioramento delle prestazioni.In alcuni casi, è possibile semplificare un confronto complesso filtrando
null
in modo esplicito i valori di una colonna, ad esempio quando nonnull
sono presenti valori 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 null
risultati vengono filtrati in modo esplicito dalla String1
colonna. EF Core può considerare in modo sicuro la colonna come non nullable durante il String1
confronto, ottenendo una query più semplice.
Uso della semantica null relazionale
È possibile disabilitare la compensazione del confronto null e usare direttamente la semantica 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à.