EF 4.1 Code-first выполняет запросы в 3 раза медленнее, чем обычные EF в моем приложении

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

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

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

Я скопировал проект и сделал обновления, поэтому у меня был бы тот же проект, но с различными реализациями Entity Framework. В новом проекте я использовал расширение Visual Studio под названием Entity Framework Power Tools для создания POCOs и 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 и пойти по-своему, но мне действительно интересно, может быть, что-то я пропустил. Любая помощь приветствуется.

Ответ 1

UPDATE

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

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

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

http://www.codetunnel.com/content/images/twotables.jpg

Это создало строку соединения, подобную этой:

<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" />

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

Увеличить изображение http://www.codetunnel.com/content/images/eftest.jpg

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

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

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

Увеличить изображение http://www.codetunnel.com/content/images/l2otest.jpg

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

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

Увеличить изображение http://www.codetunnel.com/content/images/l2emorerows.jpg

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

Увеличить изображение http://www.codetunnel.com/content/images/l2omorerows.jpg

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

Ответ 2

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

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

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

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

  • Теперь то, что вы упомянули в UPDATE для вашего собственного ответа, интересно. Если я правильно понимаю, вы сравниваете здесь EF 4.1 с DbContext с использованием Code-First (OnModelCreating имеет конфигурации сущностей, а строка подключения не имеет ссылок на файл EDMX) и EF 4.1 с помощью DbContext с использованием базы данных-First (OnModelCreating пуст (или только выбрасывает этот UnintentionalCodeFirstException из генератора DbContext), а строка подключения содержит ссылку на файл EDMX).

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

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

Edit

Подводя итог комментариям: мы говорим о пункте 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 пользователей ( "Спок 1" на "Spock 300000" ) в таблице "Пользователь", 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 сек).

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