Пытаясь перейти с EF5 на EF6, я столкнулся со значимой таблицей поиска разрыва производительности с помощью столбца с нулевым значением. Вот пример:
public class Customer
{
public int Id { get; set; }
public int? ManagerId { get; set; }
//public virtual Manager Manager { get; set; }
}
public class MyContext : DbContext
{
public MyContext(string connstring): base(connstring){}
public DbSet<Customer> Customers { get; set; }
}
class Program
{
static void Main(string[] args)
{
var db = new MyContext("CONNSTRING");
var managerId = 1234;
var q = from b in db.Customers
where b.ManagerId == managerId
select b.Id;
var s = q.ToString();
}
}
Когда EF6 генерирует SQL, он добавляет некоторую логику для нулевой обработки:
SELECT
[Extent1].[Id] AS [Id]
FROM [dbo].[Customers] AS [Extent1]
WHERE (([Extent1].[ManagerId] = @p__linq__0)
AND ( NOT ([Extent1].[ManagerId] IS NULL OR @p__linq__0 IS NULL)))
OR (([Extent1].[ManagerId] IS NULL) AND (@p__linq__0 IS NULL))
Обратите внимание, что тот же linq создал более простой SQL под EF5:
SELECT
[Extent1].[Id] AS [Id]
FROM [dbo].[Customers] AS [Extent1]
WHERE [Extent1].[ManagerId] = @p__linq__0
Я могу понять, что разработчики пытались достичь: если вы укажете null как параметр, запрос, где managerId = null, не будет выбирать строки. Я ценю заботу, но логика поиска в 99,9% разделяется: один пользовательский поиск ищет where ManagerId == null
, другой ищет определенный id where ManagerId == managerId
Проблема заключается в большом влиянии на производительность: MS SQL не использует индекс для ManagerId и происходит сканирование таблицы. Мой проект имеет сотни похожих поисков и размер базы данных около 100 ГБ общей производительности после обновления до EF6, сокращенного на 10 примерно.
Вопрос в том, кто-нибудь знает какую-то конфигурацию или соглашение, чтобы отключить этот roadblock в EF6 и создать простой sql?
EDIT:
Я проверил дюжину похожих элементов в моем проекте и обнаружил, что:
- В некоторых случаях SQL SERVER использует индекс, указанный для поля I поиск. И даже в этом случае наблюдается небольшая потеря производительности: это использует индекс дважды: сначала ищет значение, указанное в параметр, второй раз ищет нуль
-
EF6 даже проверяет значение null, когда константа точно указана как не нуль, например:
from p in db.PtnActivations where p.Carrier != "ALLTEL" where p.Carrier != "ATT" where p.Carrier != "VERIZON"
генерирует SQL
WHERE ( NOT (('ALLTEL' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL))) AND ( NOT (('ATT' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL))) AND ( NOT (('VERIZON' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL)))
который не использовал мой индекс на носителе. Версия EF5 имела
( NOT (('ALLTEL' = [Extent1].[Carrier]))) AND ( NOT (('ATT' = [Extent1].[Carrier]))) AND ( NOT (('VERIZON' = [Extent1].[Carrier]) ))
который использовал его.
Обратите внимание на условие ('ALLTEL' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL)
. Вторая часть всегда ложна, но добавление этой части дает индекс.
Мой рутинный импорт около 1,7 млн записей (что обычно занимал около 30 минут) составляет 3 часа, а прогресс составляет около 30%.