Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Введение
Базы данных SQL используют 3-значную логику (true, false, null) для выполнения сравнений, в отличие от булевой логики в C#. При переводе запросов LINQ в SQL EF Core пытается компенсировать разницу, введя дополнительные проверки null для некоторых элементов запроса.
Чтобы проиллюстрировать это, давайте определим следующую сущность:
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; }
}
и выполните несколько запросов:
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);
Первые два запроса создают простые сравнения. В первом запросе оба столбца не могут содержать NULL, поэтому проверки NULL не требуются. Во втором запросе NullableInt мог бы содержать null, но Id является обязательным к заполнению; сравнение null с ненулевым значением дает null в результате, который будет отфильтрован в ходе операции WHERE. Поэтому никаких дополнительных условий не требуется.
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]
Третий запрос вводит проверку null. Когда NullableInt равно null, результат сравнения Id <> NullableInt дает null, который будет отфильтрован операцией WHERE. Однако с точки зрения булевой логики этот случай должен включаться в результат. Поэтому EF Core добавляет необходимую проверку, чтобы убедиться в этом.
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
Запросы четыре и пять показывают шаблон, если оба столбца являются пустыми. Следует отметить, что <> операция создает более сложный (и потенциально медленный) запрос, чем == операция.
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)
Обработка значений, допускающих значение NULL, в функциях
Многие функции в SQL могут возвращать результат только в том случае, если некоторые из их аргументов являются null. EF Core использует это для создания более эффективных запросов.
Приведенный ниже запрос иллюстрирует оптимизацию:
var query = context.Entities.Where(e => e.String1.Substring(0, e.String2.Length) == null);
Созданный SQL выглядит следующим образом (нам не нужно оценивать SUBSTRING функцию, так как она будет иметь значение NULL, если один из аргументов для него имеет значение 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
Оптимизацию также можно использовать для определяемых пользователем функций. Дополнительные сведения см. на странице сопоставления определяемых пользователем функций .
Написание эффективных запросов
Сравнение столбцов, не допускающих значения NULL, проще и быстрее, чем сравнение столбцов, допускающих значение NULL. По возможности помечайте столбцы как не допускающие значения NULL.
Проверка на равенство (
==) проще и быстрее, чем проверка на неравенство (!=), так как запросу не нужно различать результатnullи результатfalse. По возможности используйте сравнение равенства. Однако простое отрицание сравнения==практически идентично!=, поэтому это не улучшает производительность.В некоторых случаях можно упростить сложное сравнение, отфильтровав
nullзначения из столбца явным образом , например, еслиnullзначения отсутствуют или эти значения не относятся к результату. Рассмотрим следующий пример:
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));
Эти запросы создают следующий 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)))
Во втором запросе результаты null в явной форме исключены из столбца String1. EF Core может безопасно обрабатывать String1 столбец как не допускающий значение NULL во время сравнения, что приводит к более простому запросу.
Использование реляционной семантики NULL
Можно отключить компенсацию сравнения NULL и напрямую использовать реляционную семантику NULL. Это можно сделать, вызвав UseRelationalNulls(true) метод в построителе параметров внутри OnConfiguring метода:
new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();
Предупреждение
При использовании реляционной семантики NULL запросы LINQ больше не имеют того же значения, что и в C#, и могут давать результаты, отличные от ожидаемых. Соблюдайте осторожность при использовании этого режима.