Сведения о производительности (Entity Framework)

В этом подразделе описаны характеристики производительности платформы ADO.NET Entity Framework, а также приведены соображения по повышению производительности приложений платформы Entity Framework.

Этапы выполнения запросов

Чтобы лучше понять специфику производительности запросов платформы Entity Framework, необходимо разобраться с операциями, которые совершаются при выполнении запроса к концептуальной модели и при возврате данных в виде объектов. В следующей таблице описан этот ряд операций.

Операция Относительные затраты Периодичность Комментарии
Загрузка метаданных Умеренно По одному разу в каждом домене приложения. Метаданные модели и сопоставления, используемые платформой Entity Framework, загружаются в MetadataWorkspace. Эти метаданные собираются глобально и доступны другим экземплярам ObjectContext в том же домене приложения.
Открытие подключения базы данных Умеренный1 При необходимости. Так как открытое подключение к базе данных использует ценный ресурс, Entity Framework открывает и закрывает подключение к базе данных только по мере необходимости. Кроме того, соединение можно открыть явно. Дополнительные сведения см. в разделе "Управление Подключение и транзакций".
Создание представлений... Высокая По одному разу в каждом домене приложения. (могут создаваться предварительно.) Прежде чем платформа Entity Framework сможет выполнять запросы к концептуальной модели или сохранять изменения в источнике данных, ей необходимо создать набор локальных представлений запросов для доступа к базе данных. В связи с высокими затратами на создание этих представлений их можно создать заранее и добавить в проект во время разработки. Дополнительные сведения см. в статье "Практическое руководство. Создание представлений для повышения производительности запросов".
Подготовка запроса Умеренный2 Один раз для каждого уникального запроса. Включает затраты на создание команды запроса, создание дерева команд на базе метаданных модели и сопоставления, а также определение вида возвращаемых данных. Теперь как команды запросов Entity SQL, так и запросы LINQ кэшируются, поэтому при последующем выполнении один и тот же запрос занимает еще меньше времени. Можно по-прежнему использовать скомпилированные запросы LINQ для снижения затрат при последующем выполнении, и скомпилированные запросы могут быть более эффективными, чем запросы LINQ, которые автоматически сохраняются в кэше. Дополнительные сведения см. в разделе "Скомпилированные запросы" (LINQ to Entity). Общие сведения о выполнении запроса LINQ см. в разделе LINQ to Entities. Примечание. Запросы LINQ to Entity, которые применяют Enumerable.Contains оператор к коллекциям в памяти, не кэшируются автоматически. Также в скомпилированных запросах LINQ не допускаются коллекции в памяти с параметрами.
Выполнение запроса Низкий2 Один раз для каждого запроса. Затраты на выполнение команды к источнику данных с помощью поставщика данных ADO.NET. Поскольку большинство источников данных кэширует планы запросов, при последующем выполнении один и тот же запрос может занимать меньше времени.
Загрузка и проверка типов Низкий3 Один раз для каждого элемента ObjectContext. Типы загружаются и проверяются относительно типов, определенных в концептуальной модели.
Отслеживание Низкий3 Один раз для каждого объекта, возвращаемого запросом. 4 Если запрос использует параметр слияния NoTracking, то этот шаг не влияет на производительность.

Если запрос использует параметры слияния AppendOnly, PreserveChanges или OverwriteChanges, то результаты запроса отслеживаются в ObjectStateManager. Для каждого отслеживаемого объекта, возвращаемого запросом, создается ключ EntityKey, который используется для создания ObjectStateEntry в ObjectStateManager. Если для ObjectStateEntry можно найти существующий объект EntityKey, то возвращается существующий объект. Если используется параметр PreserveChanges или OverwriteChanges, то объект обновляется до возвращения.

Дополнительные сведения см. в разделе "Разрешение удостоверений", "Управление состоянием" и Отслеживание изменений.
Материализация объектов Умеренный3 Один раз для каждого объекта, возвращаемого запросом. 4 Процесс считывания возвращенного объекта DbDataReader, создания объектов и установки значений свойств, основанных на значениях в каждом экземпляре класса DbDataRecord. Если объект уже существует в классе ObjectContext и в запросе используются параметры слияния AppendOnly или PreserveChanges, то этот шаг не влияет на производительность. Дополнительные сведения см. в разделе "Разрешение удостоверений", "Управление состоянием" и Отслеживание изменений.

1 Если поставщик источников данных реализует пул подключений, стоимость открытия подключения распределяется по пулу. Поставщик .NET для SQL Server поддерживает организацию пулов соединений.

2 Затраты увеличиваются с повышенной сложностью запроса.

3 Общая стоимость увеличивается пропорционально количеству объектов, возвращаемых запросом.

4 Это не требуется для запросов EntityClient, так как запросы EntityClient возвращают EntityDataReader вместо объектов. Дополнительные сведения см. в разделе EntityClient Provider для Entity Framework.

Дополнительные рекомендации

Ниже приведены дополнительные соображения по факторам, способным влиять на производительность приложений Entity Framework.

Выполнение запроса

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

Отложенное или немедленное выполнение

При создании запроса ObjectQuery<T> или LINQ запрос может выполняться не сразу. Выполнение запроса откладывается до тех пор, пока не понадобятся его результаты, например результаты перечисления foreach (C#) или For Each (Visual Basic), либо если запрос должен заполнить коллекцию List<T>. Выполнение запроса начинается немедленно при вызове метода Execute в ObjectQuery<T> либо при вызове метода LINQ, возвращающего одноэлементный запрос, например First или Any. Дополнительные сведения см. в разделе "Запросы объектов" и "Выполнение запросов" (LINQ to Entity).

Выполнение запросов LINQ на стороне клиента

Хотя запрос LINQ выполняется на том компьютере, где размещен источник данных, некоторые части запроса LINQ могут обрабатываться на клиентском компьютере. Дополнительные сведения см. в разделе "Выполнение запросов" в разделе "Выполнение запросов" (LINQ to Entities).

Сложность запросов и сопоставления

Сложность отдельных запросов и сопоставления в модели сущности значительно влияет на производительность запросов.

Сложность сопоставления

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

Сложность запросов

Запросы, требующие большого числа соединений в командах, выполняемых с источником данных или возвращающих большой объем данных, могут влиять на производительность следующим образом:

  • Запросы к концептуальной модели, которые кажутся простыми, могут приводить к выполнению более сложных запросов к источнику данных. Это может происходить как следствие преобразования платформой Entity Framework запроса к концептуальной модели в эквивалентный запрос к источнику данных. Если один набор сущностей, заданный в концептуальной модели, сопоставляется с несколькими таблицами источника данных либо если связь между сущностями сопоставлена с соединяемой таблицей, то команда запроса к источнику данных может потребовать одного или нескольких соединений.

    Примечание.

    Чтобы просмотреть команды, выполняемые по источнику данных для данного запроса, воспользуйтесь методом ToTraceString классов ObjectQuery<T> или EntityCommand. Дополнительные сведения см. в разделе "Практическое руководство. Просмотр команд Магазина".

  • Вложенные запросы Entity SQL могут создавать соединения на сервере, а также возвращать большое количество строк.

    Далее приведен пример вложенного запроса в предложении проекции:

    SELECT c, (SELECT c, (SELECT c FROM AdventureWorksModel.Vendor AS c  ) As Inner2
        FROM AdventureWorksModel.JobCandidate AS c  ) As Inner1
        FROM AdventureWorksModel.EmployeeDepartmentHistory AS c  
    

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

  • Любые запросы, возвращающие большие объемы данных, могут снижать производительность, если клиент выполняет операции, использующие ресурсы пропорционально объему результирующего набора. В таких случаях, возможно, стоит ограничить объем данных, возвращаемых запросом. Дополнительные сведения см. в разделе "Практическое руководство. Страница с помощью результатов запроса".

Любые команды, автоматически созданные платформой Entity Framework, могут оказываться сложнее аналогичных команд, написанных вручную разработчиком базы данных. Если требуется явный контроль над командами, выполняемыми с источником данных, подумайте об определении сопоставления с возвращающей табличное значение функцией или хранимой процедурой.

Связи

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

Пути запроса

По умолчанию при выполнении запроса ObjectQuery<T> связанные объекты не возвращаются (хотя возвращаются объекты, представляющие сами связи). Связанные объекты можно загрузить одним из трех способов.

  1. Задайте путь запроса до выполнения ObjectQuery<T>.

  2. Вызовите метод Load для свойства навигации, доступного для объекта.

  3. Установите параметр LazyLoadingEnabled объекта ObjectContext в значение true. Обратите внимание, что это выполняется автоматически при создании кода уровня объектов с помощью конструктора моделей данных сущности. Дополнительные сведения см. в разделе "Обзор созданного кода".

При выборе параметра помните, что придется соблюдать баланс между числом запросов к базе данных и объемом данных, возвращаемых в одном запросе. Дополнительные сведения см. в разделе "Загрузка связанных объектов".

Использование путей запроса

Пути запроса определяют граф объектов, возвращаемых запросом. При указании пути запроса для возврата всех определенных в нем объектов требуется только один запрос к базе данных. Использование путей запроса может привести к тому, что над источником данных будут выполняться сложные команды из, на первый взгляд, простых запросов к объектам. Происходит это потому, что для возвращения связанных объектов в рамках одного запроса требуется одно или несколько соединений. Сложность будет выше в запросах к сложным моделям сущностей, таким как, например, сущность с наследованием или путь, содержащий связи «многие ко многим».

Примечание.

Используйте метод ToTraceString, чтобы увидеть команду, которая будет сформирована методом ObjectQuery<T>. Дополнительные сведения см. в разделе "Практическое руководство. Просмотр команд Магазина".

Когда путь запроса содержит слишком много связанных объектов или эти объекты содержат слишком много строковых данных, источник данных может оказаться не в состоянии выполнить запрос. Это происходит, если запросу требуется промежуточное временное хранилище, которое превышает возможности источника данных. В этом случае можно снизить сложность запроса к источнику данных путем явной загрузки связанных объектов.

Явно загрузить связанные объекты можно, вызвав метод Load для свойства навигации, возвращающего EntityCollection<TEntity> или EntityReference<TEntity>. При явной загрузке объектов требуется выполнять цикл обмена данными с базой данных при каждом вызове метода Load.

Примечание.

Если при циклической обработке коллекции возвращенных объектов, например при использовании инструкции Load (foreach в Visual Basic), вызывается метод For Each, поставщик для каждого конкретного источника данных должен поддерживать несколько активных результирующих наборов на одном соединении. Для базы данных SQL Server в строке подключения поставщика необходимо указать значение MultipleActiveResultSets = true.

Можно также использовать метод LoadProperty, если сущности не имеют свойств EntityCollection<TEntity> и EntityReference<TEntity>. Это может оказаться полезным в том случае, если используются сущности POCO.

Хотя явная загрузка связанных объектов сократит число соединений и объем избыточных данных, метод Load потребует повторного соединения к базе данных, что может привести к повышении накладных расходов при загрузке большого числа объектов.

Сохранение изменений

При вызове метода SaveChanges для ObjectContext для каждого добавленного, обновленного или удаленного объекта в контексте создается отдельная команда создания, обновления или удаления. Эти команды выполняются для источника данных в единой транзакции. Как и в случае с запросами, производительность операций создания, обновления и удаления зависит от сложности сопоставления в концептуальной модели.

Распределенные транзакции

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

  • Явная транзакция с операцией для базы данных SQL Server 2000 или другого источника данных, который всегда повышает уровень явных транзакций до DTC.

  • Явная транзакция с операцией с SQL Server 2005 при управлении подключением Entity Framework. Это происходит из-за того, что SQL Server 2005 способствует DTC при закрытии и повторном открытии подключения в рамках одной транзакции, которая является поведением по умолчанию entity Framework. Такое повышение уровня до DTC не происходит при использовании SQL Server 2008. Чтобы избежать такого повышения уровня при работе с SQL Server 2005, необходимо явно открывать и закрывать соединение в пределах одной транзакции. Дополнительные сведения см. в разделе "Управление Подключение и транзакций".

Явная транзакция используется, если в пределах транзакции System.Transactions выполняется одна или несколько операций. Дополнительные сведения см. в разделе "Управление Подключение и транзакций".

Стратегии повышения производительности

Ниже перечислены методики, позволяющие повысить общую производительность запросов платформы Entity Framework.

Предварительное создание представлений

Создание представлений на базе модели сущностей - это значительная статья расходов при первом выполнении запроса приложением. С помощью программы EdmGen.exe можно заранее создавать представления в виде файлов с кодом Visual Basic или C#, которые будут добавляться в проект во время проектирования. Можно также использовать средства преобразования текстовых шаблонов для создания представлений до компиляции. Предварительно созданные представления проверяются во время выполнения, чтобы убедиться, что они соответствуют текущей версии указанной модели сущности. Дополнительные сведения см. в статье "Практическое руководство. Создание представлений для повышения производительности запросов".

При работе с очень большими моделями нужно принимать во внимание следующие соображения.

Формат метаданных .NET ограничивает число символов пользовательской строки в заданном двоичном блоке до 16 777 215 (0xFFFFFF). Если вы создаете представления для очень большой модели и файл представления достигает этого ограничения размера, вы получите ошибку компиляции "Нет логического пространства, чтобы создать дополнительные строки пользователей". Это ограничение имеет силу для всех управляемых библиотек. Дополнительные сведения см. в блоге, где показано, как избежать ошибки при работе с крупными и сложными моделями.

Рассмотрите возможность использования параметра слияния NoTracking для запросов

Для отслеживания возвращаемых объектов в контексте объекта требуются определенные затраты. Для распознавания изменений в объектах и гарантии того, что несколько запросов к одной логической сущности возвратят один экземпляр объекта, требуется, чтобы объекты были присоединены к экземпляру ObjectContext. Если вы не планируете обновлять или удалять объекты и не требует управления удостоверениями, рассмотрите возможность использования NoTracking параметров слияния при выполнении запросов.

Возвращение правильного объема данных

В некоторых случаях указание пути запроса с помощью метода Include выполняется гораздо быстрее, поскольку требуется меньше циклов обмена данными с базой данных. Однако в других сценариях дополнительные циклы обмена данными с базой данных при загрузке связанных объектов могут выполняться быстрее, поскольку более простые запросы с меньшим количеством соединений ведут к меньшей избыточности данных. В связи с этим рекомендуется проверять производительность разных способов получения связанных объектов. Дополнительные сведения см. в разделе "Загрузка связанных объектов".

Чтобы избежать возвращения слишком большого объема данных в одном запросе, можно прибегнуть к подкачке страниц результатов запроса, что даст более простые в управлении группы. Дополнительные сведения см. в разделе "Практическое руководство. Страница с помощью результатов запроса".

Ограничение области объекта ObjectContext

В большинстве случаев следует создавать экземпляр ObjectContext внутри инструкции using (Using…End Using в Visual Basic). Это может повысить производительность, поскольку гарантирует автоматическое удаление ресурсов, связанных с контекстом объекта, при выходе из блока инструкции в коде. Однако, если элементы управления привязаны к объектам, управляемым контекстом объекта, экземпляр ObjectContext следует сохранять до тех пор, пока требуется привязка, а удалять его - вручную. Дополнительные сведения см. в разделе "Управление Подключение и транзакций".

Рассмотрите возможность подключения базы данных вручную

Когда приложение выполняет ряд запросов объектов или часто вызывается SaveChanges для сохранения операций создания, обновления и удаления в источнике данных, Платформа Entity Framework должна постоянно открывать и закрывать подключение к источнику данных. В таких случаях следует попробовать вручную открывать соединение в начале этих операций и либо закрывать, либо удалять соединение по их завершении. Дополнительные сведения см. в разделе "Управление Подключение и транзакций".

Данные о производительности

Некоторые данные о производительности для Entity Framework публикуются в следующих записях в блоге команды ADO.NET:

См. также