EF 4.1 Code-first выполняет запросы в 3 раза медленнее, чем обычный EF в моем приложении - PullRequest
11 голосов
/ 30 сентября 2011

У меня есть любимый проект (простое приложение для форума), который я использую для тестирования всех новейших технологий .NET, и недавно я приступил к работе с Entity Framework Code-First. Это приложение уже имело существующее решение EF с файлом EDMX, сопоставленным с существующей базой данных, и все мои объекты были сгенерированы автоматически. Это решение отлично сработало.

Примечание: имейте в виду, что это изменение EF 4.1 предназначено исключительно для обучения. Если вам интересно, что именно мои потребности заставили меня перейти на новую версию, их не было. Я просто хотел сделать это для удовольствия.

Я скопировал проект и сделал обновления, чтобы у меня был тот же проект, но с разными реализациями Entity Framework. В новом проекте я использовал расширение Visual Studio под названием Entity Framework Power Tools для генерации POCO и DbContext из моей существующей базы данных. Все работало без нареканий. Приложение было собрано примерно через 30 минут. Довольно впечатляет.

Однако теперь, когда я запускаю приложение, я заметил, что выполнение запроса примерно в 3 раза медленнее, чем было раньше. Есть идеи, что я мог пропустить?

Ниже приведены подробные сведения для обоих решений, а также результаты измерений LINQPad для обоих. (кликните на картинку для увеличения)

EF 4.0 Подробнее

Вот снимок моей модели данных EF 4.0. Он обрезает несколько сущностей сверху и снизу, но вы поймете идею.

http://www.codetunnel.com/content/images/EF41question/1.jpg Вот тест LINQPad для моей модели данных EF 4.0.

http://www.codetunnel.com/content/images/EF41question/2.jpg Обратите внимание, что выполнение запроса заняло 2,743 секунды.

EF 4.1 Подробнее

Вот снимок моей модели данных EF 4.1. Поскольку он предназначен только для кода, я покажу класс DbContext, а также один из классов отображения (свободный код API) для одного объекта и самого объекта.

DbContext http://www.codetunnel.com/content/images/EF41question/3.jpg TopicMap (свободная конфигурация API) http://www.codetunnel.com/content/images/EF41question/4.jpg Тема (организация POCO) http://www.codetunnel.com/content/images/EF41question/5.jpg Вот тест LINQPad против моей модели EF 4.1.

http://www.codetunnel.com/content/images/EF41question/6.jpg Обратите внимание, что на этот раз для выполнения запроса потребовалось 6,287 секунды, и это был точно такой же запрос. Это занимает более 30 секунд при первом запуске. Если перейти к вкладкам SQL и IL в LINQPad, сгенерированный код SQL и код IL идентичны для обеих моделей данных. Это действительно дает мне горе. В реальном приложении с EF 4.1 все так медленно, что его невозможно использовать.

Я выполнил один и тот же запрос LINQ для обеих моделей. Запрос захватывает все темы для обычного пользователя форума, упорядочивает их в порядке убывания по дате последнего ответа (или дате публикации темы, если ответов нет).

Очевидно, я могу просто вернуться к EF 4.0 и продолжить свой веселый путь, но мне действительно интересно, может быть, я что-то пропустил.

Ответы [ 2 ]

5 голосов
/ 30 сентября 2011

ОБНОВЛЕНИЕ

Я полностью пересматриваю этот ответ из-за некоторых недавних событий.

Из-за того, что команда Entity Framework в Microsoft пыталась дублировать мою проблему, я вернулся и повторил свои шаги, чтобы лучше помочь сузить проблему.Прошло много времени с тех пор, как я задал этот вопрос, и теперь я понимаю вещи намного лучше, чем тогда.

Вместо того, чтобы вернуться и попытаться запустить какой-то очень старый код, я решил начать с нуля с простого тестапроект.Я собрал простую базу данных с двумя таблицами и сопоставил их с файлом конструктора EF 4.0.

Это сгенерировало строку подключения следующим образом:

<add name="EFTestEntities" connectionString="metadata=res://*/Entities.csdl|res://*/Entities.ssdl|res://*/Entities.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=.\sqlexpress;initial catalog=EFTest;integrated security=True;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />

Затем я заполнил базу данных 1000 строк.тестовых тем и 10 строк ответов для каждой темы.Как только я заработал эту работу, я рассчитал очень простой запрос, довольно похожий на тот, что был в моем основном вопросе.Затем я продублировал тестовый проект и изменил его, используя расширение Entity Framework Power Tools для генерации объектов моей модели и DbContext.Единственное, что я изменил, это строку подключения, чтобы удалить метаданные, на которые ссылаются, когда в проекте есть файл конструктора, поэтому он выглядел так:

<add name="EFTestContext" providerName="System.Data.SqlClient" connectionString="Data Source=.\sqlexpress;Initial Catalog=EFTest;Integrated Security=True;Pooling=False" />

Затем я выполнил точно такой же запрос, как и яс конструктором.

Разницы во времени запросов не было, за исключением небольшого дополнительного времени, которое требуется первому коду для создания метаданных отображения.После этого начального запроса две версии EF выполнялись практически одинаково.Я собирался решить проблему как не воспроизводимую, но затем я заметил, что сделал что-то ужасное в этом вопросе.Я позвонил .AsEnumerable() до моих запросов.Если вы еще не знаете, что это делает, это приведет к тому, что вся коллекция сущностей будет извлечена в память, а затем запрос будет применяться там как LINQ-to-Objects, а не LINQ-to-Entities.

Это означает, что я засосал всю таблицу в память, а затем делал там LINQ.В тех случаях, когда SQL-сервер находится на той же машине, что и ваш веб-сайт, вы можете не заметить разницы, но во многих случаях это будет огромной проблемой.В моем случае это действительно вызывало потерю производительности.

Я вернулся к своим тестам и запустил их с .AsEnumerable(), помещенным перед запросами.

Теперь я ожидал, что время будет медленнеепоскольку мои запросы LINQ не переводились в деревья выражений и не выполнялись в базе данных.Тем не менее, похоже, я воспроизвел проблему в своем вопросе.Версия только для кода возвращается намного медленнее.Это на самом деле довольно странно, потому что они оба должны работать одинаково.Я не удивлен, что они выполняются медленнее, чем когда запросы были против IQueryable, но теперь, когда они работают с IEnumerable, между ними есть большая разница.Мне удалось увеличить разницу между ними, добавив в таблицу все больше и больше данных.

Я добавил еще 5000 тем в базу данных, по 30 ответов на каждую тему.Таким образом, в настоящее время в общей сложности 6000 строк тем и 165000 строк ответов.Сначала я выполнил запрос с соответствующими LINQ-to-Entities:

Как видите, все равно разницы нет.Затем я запустил запросы с LINQ-to-Objects, используя .AsEnumerable().

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

1 голос
/ 30 сентября 2011

Если я не ошибаюсь, у вас есть две ситуации, и в обеих ситуациях вы сравниваете производительность запроса:

  1. В своем вопросе вы сравниваете производительность запроса с ObjectContext API в EF 4.0 с выполнением того же запроса с DbContext API с использованием Code-First в EF 4.1.Теперь, если я видел в ваших репозиториях кода подход EF 4.0, вы использовали EntityObject производные объекты, а не POCO (сгенерированные из генератора POCO T4 для EF 4.0), тогда как в вашем решении EF 4.1 у вас есть POCO.

    Моя гипотеза состоит в том, что это имеет значение.Если вы используете EntityObject производные объекты, ваши объекты способны отслеживать свои изменения.С другой стороны, если вы используете POCO, EF создаст моментальные снимки свойств каждого материализованного объекта в контексте, который не нужен для EntityObjects.Создание этого снимка может занять много времени.

    Так что это на самом деле не сравнение между EF 4.0 и EF 4.1, а между подходами POCO и не POCO.(Работа с POCO (которые не подготовлены для прокси-серверов отслеживания изменений, т. Е. Каждое свойство virtual) в Entity Framework медленнее во всех аспектах. Если производительность имеет значение, их следует избегать.)

  2. Теперь то, что вы упомянули в ОБНОВЛЕНИИ своего собственного ответа, интересно.Если я правильно понимаю, вы сравниваете здесь EF 4.1 с DbContext, используя Code-First (OnModelCreating имеет конфигурации объекта, а строка подключения не имеет ссылок на файл EDMX) и EF 4.1 с DbContext использование Database-First (OnModelCreating пусто (или только выдает это UnintentionalCodeFirstException из генератора DbContext), а строка подключения имеет ссылку на файл EDMX).

    InВ обоих случаях вы используете одни и те же объекты POCO.Здесь разница в производительности меня удивляет, и у меня нет непосредственной гипотезы, почему это происходит.Вы не предоставили точных измерений (как в ситуации в вашем вопросе), что было бы интересно.

    Я не удивлен, что первый запрос быстрее с подходом EDMX, потому что EF нужно только читать иобработайте файл XML для построения модели в памяти, в то время как без EDMX он должен отражать код в сборке, который, вероятно, медленнее.Но как только модель встроена в память (начиная со второго последнего запроса), я понял, что EF будет работать только с представлением модели в памяти, независимо от того, что является источником метаданных.Если у вас действительно большая разница в производительности в этой ситуации, я в тупике.

Редактировать

Подводя итог комментариям: мы говорим о точке2 выше, что означает, что вы сравниваете EF 4.1 Database-First (EDMX, указанный в строке подключения, OnModelCreating пуст) с EF 4.1 Code-First (EDMX не указан в строке подключения, OnModelCreating содержит конфигурацию модели).Результат:

Ваш запрос с EF 4.1 Database-First выполняется в три раза быстрее, чем с EF 4.1 Code-First.

Для простой модели я не смог воспроизвести поведение,Я создал один с Code-First и проверил время запроса с фиктивными данными (300000 пользователей (от «Spock 1» до «Spock 300000») в таблице User, Contains("pock") -просмотр по имени пользователя, так что все 300000 пользователей должны бытьвозвращено и материализовано, требуется 3,2 с).Затем я сгенерировал EDMX из первой модели кода:

using (var context = new MyEntities())
{
    using (var writer = new XmlTextWriter("model.edmx", Encoding.Default))
    {
        EdmxWriter.WriteEdmx(context, writer);
    }
}

Полученный файл EDMX, который я добавил в проект, изменил строку подключения для включения метаданных EDMX и снова запустил запрос.Время запроса было почти одинаковым (3,2 с).

Итак, причина проблемы, по-видимому, не столь очевидна.Это может быть связано со сложностью модели или запроса.

...