Генерация SQL EF6 для <где нулевые столбцы равны>

Пытаясь перейти с 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%.

Ответы

Ответ 1

Установите

db.Configuration.UseDatabaseNullSemantics = true;

чтобы получить поведение, которое вы имели в EF5. Этот workitem описывает, какова разница между true и false и должна помочь вам решить, согласны ли вы со старым поведением или нет.

Ответ 2

Очень отличный ответ.

Если вы используете varchar (xxx), LNQ to SQL выплевывает nvarchar (4000), который ломает индексы и конверсии, массивно выдувающие ваш sql-план. В моем случае я нашел этот вопрос из-за поведения с нечетным нулем, но это не было проблемой. В ответе ниже рассматриваются как null, так и nvarchar. SQL-план пошел от ~ 11 до .006.

public class InterestingRow
{
    [Key]
    public int interesting_row_id { get; set; }

    [StringLength(255), Required, Column(TypeName = "varchar")]
    public string public_guid { get; set; }
}

(Да, есть много причин использовать varchar, например, вы храните публично открытый guid)