Share via


Null-semantiek opvragen

Introduction

SQL-databases werken met 3-waardelogica (true, false, null) bij het uitvoeren van vergelijkingen, in plaats van de booleaanse logica van C#. Bij het vertalen van LINQ-query's naar SQL, probeert EF Core het verschil te compenseren door extra null-controles te introduceren voor sommige elementen van de query. Laten we dit illustreren door de volgende entiteit te definiƫren:

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

en geef verschillende query's op:

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 eerste twee query's produceren eenvoudige vergelijkingen. In de eerste query zijn beide kolommen niet null-baar, zodat null-controles niet nodig zijn. In de tweede query kan NullableIntnull bevatten, maar Id is non-nullable; het vergelijken van null met non-null levert null als resultaat op, wat zou worden uitgefilterd door de WHERE-bewerking. Er zijn dus ook geen aanvullende voorwaarden nodig.

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]

De derde query introduceert een null-controle. Wanneer NullableIntnull, levert de vergelijking Id <> NullableIntnull op, wat door de WHERE-bewerking zou worden uitgefilterd. Vanuit het booleaanse logicaperspectief moet deze case echter worden geretourneerd als onderdeel van het resultaat. Ef Core voegt daarom de benodigde controle toe om ervoor te zorgen.

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

Query's vier en vijf geven het patroon weer wanneer beide kolommen nullable zijn. Het is de moeite waard om te vermelden dat de <> bewerking complexere (en mogelijk tragere) query produceert dan de == bewerking.

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)

Behandeling van null-waarden in functies

Veel functies in SQL kunnen alleen een null resultaat retourneren als sommige van hun argumenten zijn null. EF Core maakt hiervan gebruik om efficiƫntere query's te produceren. De onderstaande query illustreert de optimalisatie:

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

De gegenereerde SQL is als volgt (we hoeven de SUBSTRING functie niet te evalueren, omdat deze alleen null is wanneer een van de argumenten null is).):

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

De optimalisatie kan ook worden gebruikt voor door de gebruiker gedefinieerde functies. Zie de door de gebruiker gedefinieerde pagina voor functietoewijzing voor meer informatie.

Prestatiegerichte queries schrijven

  • Het vergelijken van niet-nullbare kolommen is eenvoudiger en sneller dan het vergelijken van null-kolommen. U kunt waar mogelijk kolommen markeren als niet-nullbaar.

  • Het controleren op gelijkheid (==) is eenvoudiger en sneller dan het controleren op niet-gelijkheid (!=), omdat de query geen onderscheid hoeft te maken tussen null en false het resultaat. Gebruik waar mogelijk gelijkheidsvergelijking. Het simpelweg negeren van == vergelijkingen is echter in feite hetzelfde als !=, dus dit leidt niet tot prestatieverbetering.

  • In sommige gevallen is het mogelijk om een complexe vergelijking te vereenvoudigen door expliciet waarden uit een kolom te null filteren, bijvoorbeeld wanneer er geen null waarden aanwezig zijn of deze waarden niet relevant zijn in het resultaat. Bekijk het volgende voorbeeld:

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

Deze query's produceren de volgende 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 de tweede query null worden resultaten expliciet uit String1 de kolom gefilterd. EF Core kan de String1 kolom veilig behandelen als niet-nullbaar tijdens de vergelijking, wat resulteert in een eenvoudigere query.

Relationele null-semantiek gebruiken

Het is mogelijk om de compensatie voor null-vergelijking uit te schakelen en relationele null-semantiek rechtstreeks te gebruiken. U kunt dit doen door een methode aan te roepen UseRelationalNulls(true) in de opbouwfunctie voor opties binnen OnConfiguring de methode:

new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();

Waarschuwing

Wanneer u relationele null-semantiek gebruikt, hebben uw LINQ-query's niet langer dezelfde betekenis als in C# en kunnen andere resultaten opleveren dan verwacht. Wees voorzichtig bij het gebruik van deze modus.