Подтвердить что ты не робот

Почему EF генерирует SQL-запросы с ненужными нуль-чеками?

Я столкнулся с проблемой, когда EF создавал ужасные запросы при поиске в поле строки. Он создал запрос в стиле ленивых программистов для включения проверки нуля, который заставляет сканировать весь индекс.

рассмотрим следующие запросы.

  • Запрос 1

    var p1 = "x";
    var r1 = ctx.Set<E>().FirstOrDefault(
                            subject =>
                                p1.Equals(subject.StringField));
    
  • Запрос 2

    const string p2 = "x";
    var r2 = ctx.Set<E>().FirstOrDefault(
                            subject =>
                                p2.Equals(subject.StringField));
    

Запрос 1 производит

WHERE (('x' = [Extent2].[StringField]) OR (('x' IS NULL) AND ([Extent2].[StringField] IS NULL))) 

и выполняется за 4 секунды

Запрос 2 производит

WHERE (N'x' = [Extent2].[StringField]) 

и выполняется за 2 миллисекунды

Кто-нибудь знает о какой-либо работе? (параметр не может быть const, поскольку он вводится пользователем, но не может быть нулевым.)

N.B При профилировании оба запроса подготовлены с помощью sp_executesql по EF; если они были только что выполнены, оптимизатор запросов будет отрицать проверку OR 'x' IS NULL.

для @Martin

4b9b3361

Ответ 1

Установите UseDatabaseNullSemantics = true;

  • Когда UseDatabaseNullSemantics == true, (operand1 == operand2) будет переводиться как:

    WHERE operand1 = operand2
    
  • Когда UseDatabaseNullSemantics == false, (operand1 == operand2) будет переводиться как:

    WHERE
        (
            (operand1 = operand2)
            AND
            (NOT (operand1 IS NULL OR operand2 IS NULL))
        )
        OR
        (
            (operand1 IS NULL)
            AND
            (operand2 IS NULL)
        )
    

Это задокументировано Microsoft:

Получает или задает значение, указывающее, отображается ли нулевая семантика базы данных при сравнении двух операндов, каждый из которых потенциально может иметь значение NULL. Значением по умолчанию является false.

Вы можете установить его в конструкторе подкласса DbContext следующим образом:

public class MyContext : DbContext
{
    public MyContext()
    {
        this.Configuration.UseDatabaseNullSemantics = true;
    }
}

Или вы также можете установить этот параметр для своего экземпляра dbContext извне, как показано в примере кода ниже, с моей точки зрения (см. комментарий @GertArnold), этот apporach будет лучше, поскольку он не изменит значение по умолчанию. поведение или конфигурация базы данных):

myDbContext.Configuration.UseDatabaseNullSemantics = true;

Ответ 2

Вы можете исправить это, добавив [Required] в свойство StringField

public class Test
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string Bar{ get; set; }
    public string Foo { get; set; }

}


 string p1 = "x";
 var query1 = new Context().Tests.Where(F => p1.Equals(F.Bar));

 var query2 = new Context().Tests.Where(F => p1.Equals(F.Foo));

это query1

{ВЫБРАТЬ     [Extent1]. [Id] AS [Id],     [Extent1]. [Bar] AS [Bar],     [Extent1]. [Foo] AS [Foo]     FROM [dbo]. [Тесты] AS [Extent1]     WHERE @p__linq__0 = [Extent1]. [Bar]}

и это query2

{ВЫБРАТЬ     [Extent1]. [Id] AS [Id],     [Extent1]. [Bar] AS [Bar],     [Extent1]. [Foo] AS [Foo]     FROM [dbo]. [Тесты] AS [Extent1]     WHERE (@p__linq__0 = [Extent1]. [Foo]) ИЛИ ((@p__linq__0 IS NULL) AND ([Extent1]. [Bar2] IS NULL))}

Ответ 3

Мой коллега только что нашел действительно действительно хорошее решение. Поскольку я уже обнаружил, что использование констант дает правильный SQL. Мы задавались вопросом, можем ли мы заменить переменные в выражении с помощью констант; и, как оказалось, вы можете. Я считаю, что этот метод будет менее инвазивным, чем изменение нулевых параметров в контексте БД.

public class Foo_test : EntityContextIntegrationSpec
        {

            private static string _foo = null;

            private static DataConnection _result;

            private Because _of = () => _result = EntityContext.Set<E>().Where(StringMatch<E>(x => x.StringField));

            private static Expression<Func<TSource, bool>> StringMatch<TSource>(Expression<Func<TSource, string>> prop)
            {
                var body = Expression.Equal(prop.Body, Expression.Constant(_foo));
                return Expression.Lambda<Func<TSource,bool>>(body, prop.Parameters[0]);                
            }

            [Test] public void Test() => _result.ShouldNotBeNull();
        }