Запрос Entity Framework медленный, но тот же SQL в SqlQuery работает быстро

95

Я вижу действительно странную производительность, связанную с очень простым запросом с использованием Entity Framework Code-First с .NET framework версии 4. Запрос LINQ2Entities выглядит следующим образом:

 context.MyTables.Where(m => m.SomeStringProp == stringVar);

На это уходит более 3000 миллисекунд. Сгенерированный SQL выглядит очень просто:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = '1234567890'

Этот запрос выполняется почти мгновенно при запуске через Management Studio. Когда я меняю код C # для использования функции SqlQuery, она выполняется за 5-10 миллисекунд:

 context.MyTables.SqlQuery("SELECT [Extent1].[ID] ... WHERE [Extent1].[SomeStringProp] = @param", stringVar);

Таким образом, точно такой же SQL, в результирующих объектах отслеживаются изменения в обоих случаях, но разница в производительности между ними очень велика. Что дает?

Брайан Салливан
источник
2
Я ожидаю, что вы видите задержки инициализации - вероятно, просмотр компиляции. См. MSDN:Performance Considerations for Entity Framework 5
Николас Батлер
Я пробовал предварительно создавать просмотры, и, похоже, это не помогает. Кроме того, выполните еще один запрос EF перед медленным, чтобы исключить материал инициализации. Новый запрос выполнялся быстро, медленный по-прежнему выполнялся медленно, даже несмотря на то, что разогрев контекста произошел во время первого запроса.
Брайан Салливан
1
@marc_s - Нет, SqlQuery вернет полностью материализованный экземпляр объекта с отслеживанием изменений. См. Msdn.microsoft.com/en-us/library/…
Брайан Салливан,
Действительно ли сгенерированный SQL для вашего EF-запроса включает значение параметра или использует параметр? Это не должно влиять на скорость запроса для отдельного запроса, но со временем может привести к раздуванию плана запроса на сервере.
Джим Вули
Вы пробовали выполнить один и тот же запрос дважды / несколько раз? Сколько времени прошло при втором запуске? Вы пробовали это на .NET Framework 4.5 - в .NET Framework 4.5 есть некоторые улучшения производительности, связанные с EF, которые могут помочь.
Павел

Ответы:

97

Нашел. Оказывается, проблема в типах данных SQL. SomeStringPropСтолбец в базе данных была VARCHAR, но EF предполагает , что строковые типы .NET являются nvarchars. Результирующий процесс перевода во время запроса к БД для сравнения занимает много времени. Я думаю, что EF Prof немного сбивал меня с пути, более точное представление выполняемого запроса было бы следующим:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = N'1234567890'

Таким образом, полученное исправление состоит в том, чтобы аннотировать модель с первым кодом, указав правильный тип данных SQL:

public class MyTable
{
    ...

    [Column(TypeName="varchar")]
    public string SomeStringProp { get; set; }

    ...
}
Брайан Салливан
источник
1
Хорошее расследование. Ваш запрос страдает от "неявного преобразования", как это объясняется здесь: brentozar.com/archive/2012/07/…
Хайме
Сэкономил мне несколько часов отладки. В этом и заключалась проблема.
Cody
1
В моем случае я использую EDMX с устаревшей базой данных, которая используется varcharдля всего, и это действительно была проблема. Интересно, могу ли я сделать EDMX для рассмотрения varchar для всего строкового столбца.
Alisson
1
Великолепный мужчина. но @Jaime, что мы должны делать для первого подхода к базе данных, поскольку все (например, аннотации данных в моделях db) стирается после обновления модели EF из базы данных.
Науман Хан
Сделал это на некоторое время своей домашней страницей, чтобы на время снова пережить волнение от поиска такого отличного ответа. Спасибо!!!
OJisBad
44

Причина замедления моих запросов, сделанных в EF, заключалась в сравнении скаляров, не допускающих значения NULL, со скалярами, не допускающими значения NULL:

long? userId = 10; // nullable scalar

db.Table<Document>().Where(x => x.User.Id == userId).ToList() // or userId.Value
                                ^^^^^^^^^    ^^^^^^
                                Type: long   Type: long?

Этот запрос занял 35 секунд. Но вот такой крошечный рефакторинг:

long? userId = 10;
long userIdValue = userId.Value; // I've done that only for the presentation pursposes

db.Table<Document>().Where(x => x.User.Id == userIdValue).ToList()
                                ^^^^^^^^^    ^^^^^^^^^^^
                                Type: long   Type: long

дает невероятные результаты. Это заняло всего 50 мсек. Возможно, это ошибка в EF.

плакать
источник
13
Это так странно
Даниэль Карденас
1
О, МОЙ БОГ. Это, по-видимому, также может произойти при использовании интерфейсов IUserId.Id, которые вызывали у меня проблему, но сначала работает сопоставление Id с целым числом ... должен ли я теперь проверять все запросы в моем приложении на 100000 строк?
Дирк Бур
сообщалось об этой ошибке? Это все еще последняя версия 6.2.0
Дирк Бур
2
Та же проблема есть и в EF Core. Спасибо, что нашли это!
Янникв
Еще одно предложение - обработать переменную перед помещением в выражение LINQ. В противном случае сгенерированный sql будет намного длиннее и медленнее. Я столкнулся с тем, что внутри выражения LINQ были Trim () и ToLower (), что меня беспокоит.
samheihey
4

У меня была такая же проблема (запрос выполняется быстро из диспетчера SQL), но при выполнении из EF истекает время ожидания.

Оказывается, объект (который был создан из представления) имел неправильные ключи объекта. Таким образом, у объекта были повторяющиеся строки с одинаковыми ключами, и я предполагаю, что он должен был выполнить группировку на заднем плане.

Владимир Гедгафов
источник
3

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

Чтобы найти этот параметр (ef 6), перейдите в файл .edmx и посмотрите Свойства -> Генерация кода -> Ленивая загрузка включена. Установите значение false.

Значительное улучшение производительности для меня.

user2622095
источник
4
Это круто, но не имеет никакого отношения к вопросу о плакатах.
Джейс Реа
2

У меня тоже была эта проблема. Оказывается, в моем случае виноват анализ параметров SQL-сервера .

Первым признаком того, что моя проблема на самом деле возникла из-за обнюхивания параметров, было то, что выполнение запроса с «set arithabort off» или «set arithabort on» приводило к резко различающимся временам выполнения в Management Studio. Это связано с тем, что в ADO.NET по умолчанию используется «отключение арифметики», а в Management Studio по умолчанию - «включение арифметики». Кэш планов запроса хранит разные планы в зависимости от этого параметра.

Я отключил кеширование плана запроса для запроса, решение, которое вы можете найти здесь .

Оскар Сьёберг
источник