Entity framework способы загрузки
Entity Framework supports three ways to load related data - eager loading, lazy loading and explicit loading. The techniques shown in this topic apply equally to models created with Code First and the EF Designer.
Использование запроса для подсчета связанных сущностей без их загрузки
Иногда полезно иметь представление о количестве сущностей, связанных с другой сущностью в базе данных, не тратя на фактическую стоимость загрузки всех этих сущностей. Для этого можно использовать метод запроса с методом Count LINQ. Пример:
Многоуровневая система данных
И в конце рассмотрим более сложную многоуровневую структуру сущностей:
Теперь у каждого пользователя также есть ссылка на должность, представленную классом Position. Компания хранит ссылку на страну Country, которая хранит ссылку на столицу в виде объекта City.
Для взаимодействия с бд определим следующий контекст данных:
Теперь добавим начальные и данные и загрузим пользователей с детальными данными:
Через навигационные свойства мы можем загружать связанные данные. И здесь у нас три стратегии загрузки:
Eager loading (жадная загрузка)
Explicit loading (явная загрузка)
Lazy loading (ленивая загрузка)
В начале рассмотрим, что предствляет собой eager loading или жадная загрузка. Она позволяет загружать связанные данные с помощью метода Include() , в который передается навигационное свойство.
Например, пусть у нас есть следующие модели:
Добавим некоторые начальные данные и загрузим их из базы данных:
Для загрузки связанных данных используется метод Include:
Поскольку свойство Company в классе User является навигационным свойством, через которое мы можем получить связанную с пользователем компанию, то мы можем использовать это свойство в методе Include . На уровне базы данных это выражение будет транслироваться в следующий SQL-запрос:
То есть на уровне базы данных это будет означать использование выражения LEFT JOIN , который присоединяет данные из другой таблицы.
Консольный вывод программы:
Стоит отметить, что если данные уже ранее были загружены в контекст данных или просто ранее были в него добавлены, то можно не использовать метод Include для их получения, так как они уже в контексте. Например, возьмем выше приведенный пример:
Здесь к моменту получения пользователей компании уже загружены в констекст, поэтому нет смысла использоваться метод Include.
Теперь рассмотрим другую ситуацию:
Здесь программа логически разделена на две части: добавление объектов и их получение. Для каждой части создается свой объект ApplicationContext. В итоге при получении объект ApplicationContext не будет ничего знать об объектах, которые были добавлены в области действия другого объекта ApplicationContext. Поэтому в этом случае, если мы хотим получить связанные данные, нам необходимо использовать метод Include.
Подобным образом мы можем получить компании и подгрузить к ним связанных с ними пользователей через навигационное свойство Users в классе Company:
ThenInclude и загрузка моделей со сложной структурой
В примере выше структура моделей довольна простая - главная сущность связана с другой простой сущностью. Рассмотрим более сложную структуру моделей. Допустим, у каждой компании есть связанная сущность - страна, где находится компания:
Допустим, вместе с пользователями мы хотим загрузить и страны, в которых базируются компании пользователей. То есть получается, что нам нужно спуститься еще на уровень ниже: User - Company - Country. Для этого нам надо применить метод ThenInclude() , который работает похожим образом, что и Include:
Вначале загружаются данные пользователям. Затем загружаются связанные данные по компании. И чтобы пойти дальше по цепочке навигационных свойств, надо использовать метод ThenInclude() , через который затем подгружаются страны компаний. На уровне базы данных это выльется в следующий код SQL:
В итоге мы получим следующий консольный вывод:
Lazy Loading
Lazy loading is the process whereby an entity or collection of entities is automatically loaded from the database the first time that a property referring to the entity/entities is accessed. When using POCO entity types, lazy loading is achieved by creating instances of derived proxy types and then overriding virtual properties to add the loading hook. For example, when using the Blog entity class defined below, the related Posts will be loaded the first time the Posts navigation property is accessed:
Упреждающая загрузка нескольких уровней
Также можно заранее загрузить несколько уровней связанных сущностей. В запросах ниже приведены примеры того, как это сделать для свойств навигации коллекции и ссылки.
В настоящее время невозможно отфильтровать, какие связанные сущности загружаются. Include всегда будет переноситься во все связанные сущности.
Отключение отложенной загрузки для конкретных свойств навигации
Отложенная загрузка коллекции posts может быть отключена путем создания свойства posts, не являющегося виртуальным:
Загрузка коллекции posts может быть достигнута с помощью безотлагательной загрузки (см. статью « безотлагательная загрузка выше») или метода Load (см. раздел явная загрузка ниже).
Turn off lazy loading for all entities
Lazy loading can be turned off for all entities in the context by setting a flag on the Configuration property. For example:
Loading of related entities can still be achieved using eager loading (see Eagerly Loading above) or the Load method (see Explicitly Loading below).
Applying filters when explicitly loading related entities
The Query method provides access to the underlying query that Entity Framework will use when loading related entities. You can then use LINQ to apply filters to the query before executing it with a call to a LINQ extension method such as ToList, Load, etc. The Query method can be used with both reference and collection navigation properties but is most useful for collections where it can be used to load only part of the collection. For example:
When using the Query method it is usually best to turn off lazy loading for the navigation property. This is because otherwise the entire collection may get loaded automatically by the lazy loading mechanism either before or after the filtered query has been executed.
While the relationship can be specified as a string instead of a lambda expression, the returned IQueryable is not generic when a string is used and so the Cast method is usually needed before anything useful can be done with it.
Explicitly Loading
Even with lazy loading disabled it is still possible to lazily load related entities, but it must be done with an explicit call. To do so you use the Load method on the related entity’s entry. For example:
The Reference method should be used when an entity has a navigation property to another single entity. On the other hand, the Collection method should be used when an entity has a navigation property to a collection of other entities.
Явная загрузка
Даже при отключенной отложенной загрузке все равно можно загрузить связанные сущности с задержкой, но необходимо выполнить явный вызов. Чтобы сделать это, используйте метод Load для записи связанной сущности. Пример.
Метод Reference следует использовать, когда сущность имеет свойство навигации для другой отдельной сущности. С другой стороны, метод Collection следует использовать, когда сущность имеет свойство навигации для коллекции других сущностей.
Отключить отложенную загрузку для всех сущностей
Отложенную загрузку можно отключить для всех сущностей в контексте, установив флаг для свойства конфигурации. Пример.
Загрузка связанных сущностей по-прежнему может быть достигнута с помощью безотлагательной загрузки (см. статью « безотлагательная загрузка » выше) или метода Load (см. раздел явная загрузка ниже).
Отложенная загрузка
Отложенная загрузка — это процесс, при котором сущность или коллекция сущностей автоматически загружается из базы данных при первом обращении к свойству, ссылающимся на сущность или сущности. При использовании типов сущностей POCO отложенная загрузка достигается путем создания экземпляров производных прокси-типов и последующего переопределения виртуальных свойств для добавления обработчика загрузки. Например, при использовании класса сущности блога, определенного ниже, связанные записи будут загружаться при первом обращении к свойству навигации posts.
Загрузка сущностей со сложной многоуровневой структурой
В примере выше структура моделей довольна простая - главная сущность связана с другой простой сущностью. Рассмотрим более сложную структуру моделей. Допустим, у каждой компании есть связанная сущность - страна, где находится компания:
И пусть есть следующий контекст данных
ThenInclude
Допустим, вместе с пользователями мы хотим загрузить и страны, в которых базируются компании пользователей. То есть получается, что нам нужно спуститься еще на уровень ниже: User - Company - Country. Для этого нам надо применить метод ThenInclude() , который работает похожим образом, что и Include:
Вначале загружаются данные пользователям. Затем загружаются связанные данные по компании. И чтобы пойти дальше по цепочке навигационных свойств, надо использовать метод ThenInclude() , через который затем подгружаются страны компаний.
При загрузке связанных данных EF Core гарантирует, что если связанная сущность не установлена (например, свойство CompanyId в объекте User равно null) то данное навигационное свойство просто будет игнорироваться. Соответственно никакой ошибки в процессе получения данных не произойдет. Но поскольку компилятор не знает об этом, то он выдает предупреждение, например, в следующем случае:
В этом случае мы можем использовать оператор ! (null-forgiving оператор), чтобы указать, что значение null в данной ситуации невоможно.
В итоге на уровне базы данных это выльется в следующий код SQL:
В итоге мы получим следующий консольный вывод:
Include
Также мы можем использовать тот же метод Include для загрузки данных далее по цепочке:
В этом случае будет формироваться такой же sql-запрос, и мы получим аналогичный результат.
Способы загрузки и получения связанных данных
В Entity Framework есть три способа загрузки данных:
eager loading ("жадная загрузка")
explicit loading ("явная загрузка")
lazy loading ("ленивая загрузка")
Eager Loading
Суть Eager Loading заключается в том, чтобы использовать для подгрузки связанных по внешнему ключу данных метод Include. Например, получим всех игроков с их командами:
Без использования метода Include мы бы не могли бы получить связанную команду и ее свойства: p.Team.Name
Соответственно чтобы подгрузить к командам все данные по игрокам, мы можем написать так:
Explicit Loading
Явная загрузка предусмативает применение метода Load() для загрузки данных в контекст. Например:
Чтобы подгрузить данные, здесь идет обращение к методу db.Entry() , в который передается нужный объект. Для подгрузки связанного объекта, который не представляет коллекцию, используется метод Reference() . В этот метод переается навигационное свойство, по которому надо подгрузить данные.
Если связанные объект представляет коллекцию, то применяется метод Collection() , в который также передается навигационное свойство в виде строки.
Lazy Loading
Еще один способ представляет так называемая "ленивая загрузка" или lazy loading. При таком способе подгрузки при первом обращении к объекту, если связанные данные не нужны, то они не подгружаются. Однако при первом же обращении к навигационному свойству эти данные автоматически подгружаются из бд.
При использовании ленивой загрузки надо иметь в виду некоторые моменты при объявлении классов. Так, классы, использующие ленивую загрузку должны быть публичными, а их свойства должны иметь модификаторы public и virtual . Например, классы Player и Team могут иметь следующее определение:
В этом случае нам не потребуется использовать какие-то дополнительные методы, как Include или Load:
Через навигационные свойства мы можем загружать связанные данные. И здесь у нас три стратегии загрузки:
Eager loading (жадная загрузка)
Explicit loading (явная загрузка)
Lazy loading (ленивая загрузка)
В начале рассмотрим, что предствляет собой eager loading или жадная загрузка. Она позволяет загружать связанные данные с помощью метода Include() , в который передается навигационное свойство.
Например, пусть у нас есть следующие модели:
Добавим некоторые начальные данные и загрузим их из базы данных:
Для загрузки связанных данных используется метод Include :
Поскольку свойство Company в классе User является навигационным свойством, через которое мы можем получить связанную с пользователем компанию, то мы можем использовать это свойство в методе Include . На уровне базы данных это выражение будет транслироваться в следующий SQL-запрос:
То есть на уровне базы данных это будет означать использование выражения LEFT JOIN , который присоединяет данные из другой таблицы.
Консольный вывод программы:
Стоит отметить, что если данные уже ранее были загружены в контекст данных или просто ранее были в него добавлены, то можно не использовать метод Include для их получения, так как они уже в контексте. Например, возьмем выше приведенный пример:
Здесь к моменту получения пользователей компании уже загружены в констекст, поэтому нет смысла использоваться метод Include.
Теперь рассмотрим другую ситуацию:
Здесь программа логически разделена на две части: добавление объектов и их получение. Для каждой части создается свой объект ApplicationContext. В итоге при получении объект ApplicationContext не будет ничего знать об объектах, которые были добавлены в области действия другого объекта ApplicationContext. Поэтому в этом случае, если мы хотим получить связанные данные, нам необходимо использовать метод Include.
Подобным образом мы можем получить компании и подгрузить к ним связанных с ними пользователей через навигационное свойство Users в классе Company:
Using Query to count related entities without loading them
Sometimes it is useful to know how many entities are related to another entity in the database without actually incurring the cost of loading all those entities. The Query method with the LINQ Count method can be used to do this. For example:
Данное руководство устарело. Актуальное руководство: Руководство по Entity Framework Core
В предыдущей теме в качестве модели был использован класс Player, представляющий футболиста и содержащий четыре свойства. Однако эта была очень простая модель. В реальности в нашей базе данных может быть не одна, а несколько таблиц, которые связаны между собой различными связями.
Допустим, для каждого футболиста может быть определена футбольная команда, в которой он играет. И, наоборот, в одной футбольной команде могут играть несколько футболистов. То есть в данном случае у нас связь один-ко-многим (one-to-many).
Например, у нас определен следующий класс футбольной команды Team:
А класс Player, описывающий футболиста, мог бы выглядеть следующим образом:
Кроме обычных свойств типа Name, Position и Age здесь также определен внешний ключ. Внешний ключ состоит из обычного свойства и навигационного.
Свойство public Team Team < get; set; >в классе Player называется навигационным свойством - при получении данных об игроке оно будет автоматически получать данные из БД.
Аналогично в классе Team также имеется навигационное свойство - Players , через которое мы можем получать игроков данной команды.
Вторая часть внешнего ключа - свойство TeamId. Чтобы в связке с навигационным свойством образовать внешний ключ оно должно принимать одно из следующих вариантов имени:
Имя_навигационного_свойства+Имя ключа из связанной таблицы - в нашем случае имя навигационного свойства Team, а ключа из модели Team - Id, поэтому в нашем случае нам надо обозвать свойство TeamId, что собственно и было сделано в вышеприведенном коде.
Имя_класса_связанной_таблицы+Имя ключа из связанной таблицы - в нашем случае класс Team, а ключа из модели Team - Id, поэтому опять же в этом случае получается TeamId.
Как уже было сказано, внешний ключ позволяет получать связанные данные. Например, после генерации базы данных с помощью Code First таблица Players будет иметь следующее определение:
При определении внешнего ключа нужно иметь в виду следующее. Если тип обычного свойства во внешнем ключе определяется как int? , то есть допускает значения null , то при создании базы данных соответствующее поле так будет принимать значения NULL: [TeamId] INT NULL .
Однако если мы изменим в классе Player тип TeamId на просто int : public int TeamId < get; set; >, то в этом случае соответствующее поле имело бы ограничение NOT NULL , а внешний ключ определял бы каскадное удаление:
Многоуровневая система данных
И в конце рассмотрим более сложную многоуровневую структуру моделей:
Теперь у каждого пользователя также есть ссылка на должность, представленную классом Position. Компания хранит ссылку на страну Country, которая хранит ссылку на столицу в виде объекта City. Теперь добавим начальные и данные и загрузим пользователей с детальными данными:
Entity Framework поддерживает три способа загрузки связанной с данными загрузки, отложенной загрузки и явной загрузки. Методы, представленные в этом разделе, также применимы к моделям, созданным с помощью Code First и конструктора EF.
Turn lazy loading off for serialization
Lazy loading and serialization don’t mix well, and if you aren’t careful you can end up querying for your entire database just because lazy loading is enabled. Most serializers work by accessing each property on an instance of a type. Property access triggers lazy loading, so more entities get serialized. On those entities properties are accessed, and even more entities are loaded. It’s a good practice to turn lazy loading off before you serialize an entity. The following sections show how to do this.
Turning off lazy loading for specific navigation properties
Lazy loading of the Posts collection can be turned off by making the Posts property non-virtual:
Loading of the Posts collection can still be achieved using eager loading (see Eagerly Loading above) or the Load method (see Explicitly Loading below).
Упреждающая загрузка
Упреждающая загрузка — это процесс, при котором запрос для одного типа сущности также загружает связанные сущности в составе запроса. Упреждающая загрузка достигается с помощью метода Include. Например, приведенные ниже запросы будут загружать блоги и все записи, связанные с каждым блогом.
Include — это метод расширения в пространстве имен System. Data. Entity, поэтому убедитесь, что вы используете это пространство имен.
Eagerly loading multiple levels
It is also possible to eagerly load multiple levels of related entities. The queries below show examples of how to do this for both collection and reference navigation properties.
It is not currently possible to filter which related entities are loaded. Include will always bring in all related entities.
Eagerly Loading
Eager loading is the process whereby a query for one type of entity also loads related entities as part of the query. Eager loading is achieved by use of the Include method. For example, the queries below will load blogs and all the posts related to each blog.
Include is an extension method in the System.Data.Entity namespace so make sure you are using that namespace.
Отключить отложенную загрузку для сериализации
Отложенная загрузка и сериализация не сочетаются хорошо, и если вы не следите за тем, чтобы выполнять запросы для всей базы данных, просто потому, что включена отложенная загрузка. Большинство сериализаторов работают путем доступа к каждому свойству в экземпляре типа. Доступ к свойствам активирует отложенную загрузку, поэтому сериализуются другие сущности. Для доступа к этим свойствам сущностей и загружается еще несколько сущностей. Перед сериализацией сущности рекомендуется отключить отложенную загрузку. В следующих разделах показано, как это сделать.
Применение фильтров при явной загрузке связанных сущностей
Метод Query предоставляет доступ к базовому запросу, который Entity Framework будет использовать при загрузке связанных сущностей. Затем можно использовать LINQ для применения фильтров к запросу перед его выполнением с вызовом метода расширения LINQ, такого как ToList, Load и т. д. Метод Query можно использовать со свойствами навигации по ссылке и коллекции, но наиболее удобен для коллекций, где его можно использовать для загрузки только части коллекции. Пример.
При использовании метода запроса обычно лучше отключить отложенную загрузку для свойства навигации. Это происходит потому, что в противном случае вся коллекция может загружаться автоматически механизмом отложенной загрузки либо до, либо после выполнения фильтрованного запроса.
Несмотря на то, что связь может быть указана в виде строки, а не лямбда-выражения, возвращаемый IQueryable не является универсальным, если используется строка, поэтому метод Cast обычно необходим, прежде чем можно будет выполнить с ним какое-либо полезное.
Читайте также: