NULL-Semantik in AbfragenQuery null semantics

EinführungIntroduction

Im Gegensatz zur booleschen Logik von C# arbeiten SQL-Datenbanken bei Vergleichen mit einer dreiwertigen Logik (true, false, null).SQL databases operate on 3-valued logic (true, false, null) when performing comparisons, as opposed to the boolean logic of C#. Beim Übersetzen von LINQ-Abfragen in SQL versucht EF Core, den Unterschied zu kompensieren, indem zusätzliche NULL-Überprüfungen für einige Elemente der Abfrage eingeführt werden.When translating LINQ queries to SQL, EF Core tries to compensate for the difference by introducing additional null checks for some elements of the query. Zur Veranschaulichung wird die folgende Entität definiert:To illustrate this, let's define the following entity:

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

Außerdem werden mehrere Abfragen ausgegeben:and issue several queries:

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

Die ersten beiden Abfragen erzeugen einfache Vergleiche.The first two queries produce simple comparisons. In der ersten Abfrage sind beide Spalten Non-Nullable, sodass keine NULL-Überprüfungen erforderlich sind.In the first query, both columns are non-nullable so null checks are not needed. In der zweiten Abfrage kann NullableInt den Wert null enthalten. Id ist jedoch Non-Nullable. Ein Vergleich von null mit Nicht-NULL ergibt null, wobei das Ergebnis durch den WHERE-Vorgang herausgefiltert wird.In the second query, NullableInt could contain null, but Id is non-nullable; comparing null to non-null yields null as a result, which would be filtered out by WHERE operation. Es sind also auch keine zusätzlichen Bedingungen erforderlich.So no additional terms are needed either.

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]

Die dritte Abfrage führt eine NULL-Überprüfung ein.The third query introduces a null check. Wenn NullableInt null entspricht, ergibt der Vergleich Id <> NullableInt null, wobei das Ergebnis durch den WHERE-Vorgang herausgefiltert wird.When NullableInt is null the comparison Id <> NullableInt yields null, which would be filtered out by WHERE operation. Aus der Perspektive der booleschen Logik sollte dieser Fall jedoch als Teil des Ergebnisses zurückgegeben werden.However, from the boolean logic perspective this case should be returned as part of the result. Daher wird in EF Core die erforderliche Überprüfung hinzugefügt, um dies sicherzustellen.Hence EF Core adds the necessary check to ensure that.

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

Die vierte und fünfte Abfrage weisen ein Muster auf, in dem beide Spalten Nullwerte zulassen.Queries four and five show the pattern when both columns are nullable. Beachten Sie, dass der <>-Vorgang eine kompliziertere (und potenziell langsamere) Abfrage erzeugt als der ==-Vorgang.It's worth noting that the <> operation produces more complicated (and potentially slower) query than the == operation.

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)

Verarbeitung von Nullwerte zulassenden Variablen in FunktionenTreatment of nullable values in functions

Viele Funktionen in SQL können nur dann ein null-Ergebnis zurückgeben, wenn einige ihrer Argumente null sind.Many functions in SQL can only return a null result if some of their arguments are null. EF Core nutzt dies, um effizientere Abfragen zu erzeugen.EF Core takes advantage of this to produce more efficient queries. Die folgende Abfrage veranschaulicht die Optimierung:The query below illustrates the optimization:

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

Der generierte SQL-Code lautet wie folgt (die SUBSTRING-Funktion muss nicht ausgewertet werden, da sie nur NULL ist, wenn eines ihrer Argumente NULL lautet):The generated SQL is as follows (we don't need to evaluate the SUBSTRING function since it will be only null when either of the arguments to it is 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

Die Optimierung kann auch für benutzerdefinierte Funktionen verwendet werden.The optimization can also be used for user-defined functions. Weitere Informationen finden Sie unter Zuordnung von benutzerdefinierten Funktionen.See user defined function mapping page for more details.

Schreiben von leistungsstarken AbfragenWriting performant queries

  • Das Vergleichen von Non-Nullable-Spalten ist einfacher und schneller als das Vergleichen von Nullwerte zulassenden Spalten.Comparing non-nullable columns is simpler and faster than comparing nullable columns. Markieren Sie Spalten, wenn möglich, immer als Non-Nullable.Consider marking columns as non-nullable whenever possible.

  • Die Überprüfung auf Gleichheit (==) ist einfacher und schneller als die Überprüfung auf Ungleichheit (!=), da die Abfrage nicht zwischen null- und false-Ergebnissen unterscheiden muss.Checking for equality (==) is simpler and faster than checking for non-equality (!=), because query doesn't need to distinguish between null and false result. Verwenden Sie nach Möglichkeit den Gleichheitsvergleich.Use equality comparison whenever possible. Den Vergleich == einfach zu negieren ist jedoch identisch mit != und führt nicht zu einer Leistungsverbesserung.However, simply negating == comparison is effectively the same as !=, so it doesn't result in performance improvement.

  • In einigen Fällen ist es möglich, einen komplexen Vergleich zu vereinfachen, indem null-Werte explizit aus einer Spalte herausgefiltert werden, z. B. wenn keine null-Werte vorhanden oder diese im Ergebnis nicht relevant sind.In some cases, it is possible to simplify a complex comparison by filtering out null values from a column explicitly - for example when no null values are present or these values are not relevant in the result. Betrachten Sie das folgende Beispiel:Consider the following example:

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

Diese Abfragen erzeugen den folgenden SQL-Code:These queries produce the following 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)))

In der zweiten Abfrage werden null-Ergebnisse explizit aus der Spalte String1 herausgefiltert.In the second query, null results are filtered out from String1 column explicitly. EF Core kann die Spalte String1 beim Vergleich gefahrlos als Non-Nullable behandeln, was zu einer einfacheren Abfrage führt.EF Core can safely treat the String1 column as non-nullable during comparison, resulting in a simpler query.

Verwenden der relationalen NULL-SemantikUsing relational null semantics

Es ist möglich, die Kompensierung von NULL-Vergleichen zu deaktivieren und direkt eine relationale NULL-Semantik zu verwenden.It's possible to disable the null comparison compensation and use relational null semantics directly. Dazu muss die Methode UseRelationalNulls(true) für OptionsBuilder innerhalb Methode OnConfiguring aufgerufen werden:This can be done by calling UseRelationalNulls(true) method on the options builder inside OnConfiguring method:

new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();

Warnung

Wenn Sie die relationale NULL-Semantik verwenden, haben Ihre LINQ-Abfragen nicht mehr dieselbe Bedeutung wie in C#, und es kann zu unerwarteten Ergebnissen kommen.When using relational null semantics, your LINQ queries no longer have the same meaning as they do in C#, and may yield different results than expected. Gehen Sie umsichtig vor, wenn Sie diesen Modus verwenden.Exercise caution when using this mode.