Entity framework способы загрузки
Зачем использовать запросы на Entity SQL, если сущности можно получить запросами LINQ?
На каждой презентации, посвященной использованию Entity SQL с EntityClient или службами Object Services, кто-нибудь задает этот вопрос. (И я не могу их осуждать, потому что это один из первых вопросов, которые я задавал сам себе, начав освоение Entity Framework!) Строгий контроль типов и синтаксис запросов LINQ настолько притягательны, что разработчики могут только удивляться, зачем им нужен новый язык для взаимодействия с сущностями.
Чтобы ответить на этот вопрос наиболее полно, я хотел бы прежде всего обсудить три главных приема работы с EDM.
- Составление запросов Entity SQL с помощью поставщика EntityClient
- Составление запросов Entity SQL с помощью служб Object Services
- Составление запросов LINQ с помощью служб Object Services
У всех этих методов есть общие характеристики, например прямое или косвенное использование поставщика EntityClient. Однако различаются как результаты применения данных приемов, так и способы получения этих результатов.
Так почему же используется Entity SQL, если есть LINQ? Ответ заключается в преимуществах и недостатках обоих методов.
EntityClient и Entity SQL
Код, написанный с помощью EntityClient API, из всех трех методов обеспечивает наиболее точный контроль. Можно создать EntityConnection для соединения с EDM, написать запрос на Entity SQL и выполнить его с помощью EntityCommand, а потом вернуть результат через DbDataReader. За счет отсутствия некоторых полезных особенностей синтаксиса, имеющихся в LINQ и службах Object Services, этот способ является также менее ресурсоемким.
Самое большое преимущество Entity SQL — гибкость. Его основанный на строках синтаксис предполагает легкое построение динамических запросов. Это очень полезно для создания оперативных запросов.
Однако другой стороной гибкости и легкости является то, что данные можно вернуть только через DbDataReader. С помощью EntityClient с Entity SQL нельзя возвращать настоящие сущности. Для извлечения данных и обхода коллекции записей, удовлетворяющих запросу Entity SQL, используется класс DbDataReader. Из кода на рис. 1 видно, что через DbDataReader можно перебирать только записи о клиентах, а не сущности Клиент.
Рис. 1 Обход по строками через DbDataReader
Сопоставимого языка манипулирования данными (ЯМД) для Entity SQL пока нет. Это значит, что операторы Insert, Update или Delete нельзя применять к EDM напрямую (см. рис. 2).
Рис. 2 Entity Framework. API
EntityClient и Entity SQL | Службы Object Services и Entity SQL | Службы Object Services и LINQ | |
---|---|---|---|
Напрямую к поставщику EntityClient | Да | Нет | Нет |
Хорош для оперативных запросов | Да | Да | Нет |
Может выдавать DML напрямую | Нет | Нет | Нет |
Строго типизирован | Нет | Нет | Да |
Может возвращать сущности как результат | Нет | Да | Да |
Службы Object Services и Entity SQL
Следующий метод предполагает использовать службы Object Services для выполнения запросов Entity SQL. Этот метод отходит от прямого взаимодействия с поставщиком EntityClient (хотя с этим поставщиком происходит скрытый обмен данными). Чтобы выдавать запросы к EDM, используются ObjectContext и ObjectQuery<T>.
Этот прием так же хорош для выдачи оперативных запросов, как и первый. Однако, используя службы Object Services с Entity SQL, можно получать из EDM сущности, а не просто данные через DbDataReader. Получается добротное сочетание гибкости запросов и возврата настоящих сущностей.
Поскольку настоящее время конструкции ЯМД в Entity SQL отсутствуют, то выдавать запросы с командами вставки, модификации или удаления с помощью Entity SQL и служб Object Services нельзя. Однако рассматриваемый метод позволяет получить сущность из EDM, а затем модифицировать ее методом SaveChanges класса ObjectContext. В следующем коде приводится пример обхода коллекции сущностей «Заказчик»:
Службы Object Services и LINQ
Использование служб Object Services с LINQ не так удобно для составления оперативных запросов, как другие методы. Следующий код возвращает коллекцию сущностей «Заказчик» из EDM:
Как и Entity SQL, LINQ не поддерживает операторы ЯМД напрямую. Пока модифицировать сущности в базе данных можно только с помощью служб Object Services (используя метод SaveChanges), что осуществляется возвратом сущностей из EDM, изменения которых отслеживает Entity Framework. Короче говоря, ни LINQ, ни Entity SQL не выполняют операций модификации; только объект класса ObjectContext может их выполнять.
Сводная таблица различий разных методов приведена на рис. 2. Так почему же может потребоваться использовать Entity SQL, когда уже есть LINQ? Entity SQL — это хороший выбор в случаях, когда нужно составить или оперативный, или более гибкий, чем позволяет LINQ, запрос. В других случаях я предлагаю использовать LINQ со службами Object Services, тем самым сочетая преимущества строгого контроля типов и возможности возвращать сущности и проекции.
Вместе с синтаксисом, поддерживающим строгую типизацию, LINQ позволяет видеть многие ошибки во время разработки запроса, а не ловить их, запуская приложение. Мне очень нравится эта особенность — она позволяет писать код вместо того, чтобы постоянно собирать и запускать приложение, вылавливая в нем ошибки.
Какова роль объектов класса ObjectContext?
ObjectContext является шлюзом к EntityConnection для служб Object Services. Он предоставляет доступ к EDM через нижележащий EntityConnection. Например, можно получить доступ к сущностям через ObjectContext, из ObjectContext добыть информацию о состоянии объектов, а потом методом CreateQuery создать ObjectQuery<T>.
Кроме того, ObjectContext предоставляет объектам способ получать информацию об обновлениях записей базы данных. Например, с помощью методов ObjectContext можно добавлять сущности к ObjectContext, удалять их, манипулировать ими и, в конце концов, сохранить все изменения сущностей в базу данных (методом SaveChanges).
Как работает в Entity Framework явная и упреждающая загрузка?
Явная загрузка — это поведение LINQ по умолчанию для Entities и Entity Framework. Когда в Entity Framework исполняется запрос, полностью доступны только сущности, возвращаемые запросом, а связанные с ними сущности не загружаются. Например, запрос, который извлекает из EDM все заказы, выберет все записи с заказами и вернет коллекцию сущностей «Заказ». Однако запись о заказчике, связанная с заказом, этим запросом извлечена не будет, вследствие чего соответствующая этому заказу сущность «Заказчик» не будет загружена. Таким образом, в приведенном образце кода при попытке обратиться к заказчику заказа будет выдано исключение, потому что сущность «Заказчик» не будет загружена:
В Entity Framework для каждого экземпляра класса EntityReference есть метод Load. Этот метод позволяет явно загрузить коллекцию, связанную с другой сущностью. Например, в предыдущем коде можно сообщить Entity Framework, что записи заказчиков для заказа необходимо загружать. Измененный код, осуществляющий явную загрузку, показан на рис. 3. Прежде всего в нем проверяется, загружены ли сущности «Заказчик». Если нет, они загружаются для данного заказа. Такой прием называется явной загрузкой.
Рис. 5 Использование ToTraceString
В примере на рис. 3 запрос SQL, получающий запись о заказчике, будет выполняться для каждого обрабатываемого заказа. В случае обхода сотен или даже десятков заказов это приведет к большому количеству отдельных запросов к базе данных. Если известно, что данных, выбранных запросом, будет недостаточно и к ним потребуются дополнительные данные (например, информация о заказчике к данным о заказе), необходимую информацию можно получить заранее.
Рис. 3 Явная загрузка
Некоторые дополнительные идеи по этой теме от группы разработчиков Entity Framework можно посмотреть на врезке «Дополнительная информация: загрузка данных в Entity Framework
На рис. 4 показан прием, называемый упреждающей загрузкой. Метод Include, применяемый к сущности «Заказ» в LINQ запросе, принимает аргумент, который указывает запросу извлечь данные не только для заказа, но и для связанного с этим заказом заказчика. Этот метод позволяет создать один оператор SQL, который загрузит все данные о заказах и заказчиках, удовлетворяющие условиям запроса LINQ.
Рис. 4 Упреждающая загрузка.
Нужно понимать, насколько важно учитывать тип загрузки. При использовании метода Load (как показано на рис. 3) для явной загрузки сущностей во время обхода коллекции, к базе данных будет направлено несколько запросов — по одному на каждый вызов метода Load.
Этот метод отлично подходит, если требуется только один – два раза получить доступ к данным. Однако упреждающая загрузка с использованием метода Include (как показано на рис. 4) намного больше подходит в ситуациях, когда доступ к некоторой сущности и к сущностям, связанным с ней, требуется постоянно.
Лучший способ выяснить, какой способ подходит больше, — профилировать производительность в разных вариантах. Как и в большинстве случаев, решение о том, использовать упреждающую или явную загрузку, зависит от ситуации.
Как увидеть подготовленный к выполнению запрос SQL?
Мне часто хотелось посмотреть, как выглядит исполняемый SQL, соответствующий запросу, сделанному с помощью ObjectQuery, и я обнаружил два полезных приема. Первый — это использовать профилировщик для SQL Server® (или любое другое доступное вам средство баз данных профилирования). Второй прием — использовать метод класса ObjectQuery, который называется ToTraceString.
На рис.5 показано, как вызвать метод ToTraceString для ObjectQuery<T>. Обратите внимание, что соединение ObjectContext открыто (EntityConnection). Метод ToTraceString требует открытого соединения, чтобы можно было определить исполняемый запрос. Метод ToTraceString доступен как для класса ObjectQuery<T>, так и для EntityCommand.
Что можно сделать со сложными типами данных?
В Entity Framework есть структура данных, называемая сложным типом, которая служит для представления набора свойств, тесно связанных между собой. Рассмотрим тип «адрес». Сложный тип можно использовать для создания типа «адрес» для заказчика, так что связанные с адресом свойства сущности «заказчик» (город, область и номер телефона) окажутся внутри этого типа, а не на уровне типа «заказчик». С помощью сложных типов осуществляется логическая группировка похожих скалярных свойств, что упрощает поиск тесно связанных свойств сущности и сохраняет эту логическую группировку в EDM. Как и сущности, комплексные типы содержат скалярные свойства, однако в отличие от свойства у них нет идентификатора (значений ключа) и их нельзя сохранять в базу данных через ObjectContext. Комплексные типы всего лишь стороны сущностей, но не сами сущности. Это хороший инструмент для группировки логически связанных свойств сущности.
Как создать сложный тип?
Поскольку построитель EDM пока не поддерживает визуальное создание сложных типов, их приходится создавать, редактируя файл EDMX в редакторе XML. Прежде всего, надо создать сложный тип в языке определения концептуальных схем (CSDL) На рис.6 показано представление типа «Заказчик» в CSDL вместе с вновь созданным сложным типом адреса. Обратите внимание, что в записях о заказчиках находятся поля адреса, города и области, а все остальные свойства, связанные с адресом, удалены и заменены свойством «О». Новое свойство адрес имеет сложный тип AddressType.
Рис. 6 Создание ComplexType
Следующим шагом является отображение в язык спецификации отображений (MSL), чтобы новый сложный тип был учтен. На рис.7 показано отображение типа «заказчик» вместе с вновь созданным сложным типом адреса. После сборки проекта к полям сложного типа можно будет обращаться в коде. Например, следующим LINQ-запросом можно получить список заказов только для лондонских заказчиков:
Выводы
В выпуске этого месяца я сравнил использование EntityClient и служб Object Services с Entity SQL и LINQ. Я также коснулся того, как и зачем создавать сложные типы и продемонстрировал, как работает упреждающая и явная загрузка. Я получил так много вопросов от разработчиков, интересующихся Entity Framework, что намереваюсь обсудить похожие темы и дать практические советы в будущих статьях в Data Points.
Из-за того, что данные не сразу же загружаются, lazy loading и explicit loading имеют общее название deferredloading.
В целом, если у вас есть необходимость в данных для каждой сущности, метод eager loading предлагает наилучшую производиетльность потому, что один запрос обычно эффективнее нежели множество запросов для каждой сущности. Для примера, представьте, что на каждом факультете проводится десять курсов. Пример для eager loading будет сопровождаться одним join-запросом. Примеры для lazy loading и explicit loading будут сопровождаться одиннадцатью.
С другой стороны, если доступ к navigation properties сущностям осуществляется редко или используется малое количество сущностей, lazy loading может быть эффективнее — eager loading будет загружать больше данных чем нужно. Обычно explicit loading используется только тогда, когда выключен lazy loading. Допустим, есть ситуация, когда lazy loading может быть выключен в процессе сериализации, когда вы уверены, что все navigation properties вам не нужны. Если lazy loading включен, все navigation properties загрузятся автоматически, так как сериализация обращается ко всем свойствам.
- При объявлении navigation properties пропустите указание модификатора virtual.
- Для всех navigation properties укажите LazyLoadingEnabled как false.
Создание страницы CoursesIndex
Сущность Course имеет navigation property, содержащую в себе сущность Department – сущность факультета, которому принадлежит данный курс. Чтобы отобразить имя факультета, нужно обратиться к свойству Name соответствующей сущности.
Создайте контроллер типа Course:
В Controllers\CourseController.cs обратите внимание на метод Index:
Автоматический scaffolding определяет использование eager loading для Department navigation property с помощью метода Include.
В Views\Course\Index.cshtml замените код на:
Были внесены следующие изменения:
- Заголовок изменён на Courses.
- Строки выровнены по левому краю.
- Добавлен стобец под заголовком Number, отображающий значение свойства CourseID. (Первичные ключи обычно не включаются в страницу, так как не имеют смысла, но в данном случае значение осмысленное и его необходимо показать.
- Заголовок последнего столбца изменен на Department.
Выберите пункт Courses чтобы увидеть список с названиями факультетов.
Создание InstructorsIndexPageсо списком курсов и слушателей этих курсов
Вы создадите контроллер и представление для сущности Instructor для отображения страницы Instructors Index:
- Список преподавателей отображает соответствующие данные сущности OfficeAssignment. Сущности Instructor и OfficeAssignment связаны один-к-нулю-или-к-одному. Для сущности OfficeAssignment используется eager loading.
- Когда пользователь выбирает преподавателя, отображаются связанные сущности Course. Сущности Instructor и Course связаны многие-ко-многим. Вы будете использовать eager loading для сущностей Course и связанным с ними сущностей Department. В этом случае lazy loading будет более эффективно, потому что необходимо выгружать данные о курсах только для выбранного преподавателя. Однако в примере показано использование eager loading.
- Когдап ользователь выбирает курс, отображаются данные сущности Enrollments. Сущности Course и Enrollment связаны один-ко-многим. Вы добавите explicit loading для сущности Enrollment и связанным с ней сущностей Student. (При включенном lazy loading использование Explicit loading необязательно, но мы просто покажем как работает explicit loading.)
На странице Instructor Index отображается три различных таблицы. Для этого мы создадим модель представления, включающую в себя три свойства, каждое из которых содержит в себе данные для каждой из таблиц.
В папке ViewModels создайте InstructorIndexData.cs:
Стили для выделенных столбцов
Для выделенных столбцов необходим отдельный цвет фона. Для того, чтобы сделать это, добавьте следующий код в Content\Site.css:
Создание контроллера и представлений для Instructor
Создайте контроллер типа Instructor:
В Controllers\InstructorController.cs добавьте using для ViewModels:
Сгенерированный код в методе Index определяет использование eager loading только для OfficeAssignment navigation property:
Замените метод Index следующим кодом, который загружает дополнительные данные и кладёт их в модель представления:
Метод принимает опциональные строковые параметры: ID значений выбранных преподавателя и курса, и затем передаёт необходимые данные в представление. ID поступают от ссылок Select на странице.
Код начинается созданием экземпляра модели представления и передачи в него списка преподавателей:
Мы определяем eager loading для Instructor.OfficeAssignment и Instructor.Courses navigation property. Для связанных сущностей Course eager loading определяется для Course.Department navigation property с использованием метода Select в методе Include. Результаты сортируются по фамилии.
Если выбран преподаватель, то данный преподаватель извлекается из списка преподавателей в модели данных, после чего свойство Courses инициализируется сущностями Course из соответствующей преподавателю Courses navigation property.
Метод Where возвращает коллекцию, но в данном случае условие, переданное методу, указывает на то, чтобы возвратить только одну сущность Instructor. Метод Single конвертирует коллекцию в одну сущность Instructor, что позволяет обратиться к соответствующему данной сущности свойству Courses.
Метод Single используется на коллекции если известно, что коллекция будет состоять из одного элемента. Данный метод выбрасывает исключения, если коллекция пустая или состоит из более чем одного элемента. Однако и в данном случае будет выброшено исключение (из-за свойства Course с ссылкой null). При вызове Single вместо отдельного вызова Where можно передать само условие:
.Single(i => i.InstructorID == id.Value)
.Where(I => i.InstructorID == id.Value).Single()
Далее, если выбран курс, то этот курс извлекается из списка курсов в модели. Затем свойство модели Enrollments инициализируется сущностями Enrollments navigation property.
И, наконец, возвращение в модель:
Редактированиепредставления Instructor Index
В Views\Instructor\Index.cshtml замените код на:
- Заголовок страницы изменён на Instructors.
- Moved the row link columns to the left.
- Убран столбец FullName.
- Добавлен столбец Office, отображающий item.OfficeAssignment.Location в том случае, если item.OfficeAssignment не null. (Из-за того, что здесь связь один-к-нулю-или-одному, с сущностью может быть не связана ни одна сущность OfficeAssignment.)
- Добавлен код, динамически добавляющий в контейнер tr выбранного преподавателя. Таким образом мы задаём цвет фона, используя CSS-класс. (атрибут valign будет полезен в будущем, когда мы добавим многостроковый столбец в таблицу)
- Перед ссылками в каждой строке добавлен новый ActionLink Select, что позволяет передать ID выбранного преподавателя в метод Index.
в Views\Instructors\Index.cshtml после контейнера table добавьте код, отображающий список курсов выбранного преподавателя.
Код загружает свойство модели представления Courses для отображения списка курсов, и отображает ссылку Select, с помощью которой в метод Index передаётся ID выбранного курса.
Выберите преподавателя, и увидите таблицу с курсами данного преподавателя и факультеты, на которых ведутся курсы.
Note если выбранная строка не меняет цвет, обновите страницу, иногда это нужно для загрузки файла .css.
После кода, который вы добавили, добавьте код, отображающий список студентов, учащихся на выбранном курсе.
Код загружает свойство модели представления Enrollments для отображения списка учащихся на выбранном курсе студентов.
Выберите преподавателя и щёлкните на курсе, чтобы увидеть учащихся студентов и их оценки.
Добавление ExplicitLoading
В InstructorController.cs и обратите внимание на то, как метод Index загружает список учащихся на выбранном курсе:
Во время загрузки списка преподавателей вы определили eager loading для Courses navigation property и свойства каждого из курсов Department, после чего отправили коллекцию Courses в модель представления, и теперь загружаете Enrollments navigation property из одной из сущностей в этой коллекции. Из-за того, что вы не определили eager loading для Course.Enrollments navigation property, данные этого свйоства появляются на странице в результате lazy loading.
Если отключить lazy loading, свойство Enrollments будет равно null независимо от того, сколько учащихся учится на этом курсе. В этом случае, чтобы инициализировать свойство Enrollments, вы должны определить для него eager loading или explicit loading. Для определения explicit loading замените код метода Index на:
После загрузки выбранной сущности Course, новый код явно загружает свойства Enrollments:
Затем явно загружаются сущности Student:
Обратите вниамние на то, что вы используете метод Collection для инициализации свойства коллекции, но для свойства с одним элементом вы используете метод Reference. Теперь можно открыть страницу Instructor Index – ничего не изменилось внешне, но изменился принцип загрузки данных.
Итак, вы использовали все три метода загрузки данных в navigation properties. В следующем уроке вы научитесь обновлять связанные данные.
Теперь мы можем начать запрашивать данные из базы данных с помощью EF Core. Каждый запрос состоит из трех основных частей:
- Подключение к базе данных через свойство ApplicationContext DbSet
- Серия команд LINQ и / или EF Core
- Выполнение запроса
Вторая часть используется часто, но иногда ее можно пропустить, если мы хотим вернуть все строки из таблицы, к которой мы подключаемся через свойство DbSet.
Итак, чтобы объяснить основы запросов, мы собираемся использовать контроллер Values , как мы это делали в первой части серии и только действие Get для простоты. Мы собираемся сосредоточиться на логике EF Core, а не на веб-API в целом.
. Поэтому давайте добавим наш объект контекста в конструктор Values и напишем первый запрос в действии Get:
Из этого запроса мы можем увидеть все упомянутые части. _context.Students - это первая часть, где мы получаем доступ к таблице Student в базе данных через свойство DbSet<Student> Students .
Where(s => s.Age> 25) - вторая часть запроса, в которой мы используем команду LINQ для выбора только необходимых строк. Далее, у нас есть метод ToList() , который выполняет этот запрос.
СОВЕТ: когда мы пишем запросы только для чтения в Entity Framework Core (результат запроса не будет использоваться для каких-либо дополнительных изменений базы данных), мы всегда должны добавлять AsNoTracking способ ускорить выполнение.
В следующей статье мы поговорим о том, как EF Core изменяет данные в базе данных и отслеживает изменения в загруженной сущности. На данный момент просто знайте, что EF Core не будет отслеживать изменения (когда мы применяем AsNoTracking) в загруженном объекте, что ускорит выполнение запроса:
Различные способы построения реляционных запросов
Существуют разные подходы к получению наших данных:
- Eager loading - жадная загрузка
- Explicit Loading - явная загрузка
- Select (Projection) loading - выборка (проекция)
- Lazy loading - ленивая загрузка
В результате нашего запроса значения свойств навигации равны нулю:
Запросы реляционной базы данных с жадной загрузкой в EF Core
При использовании подхода "Активная загрузка" EF Core включает взаимосвязи в результат запроса. Для этого используются два разных метода: Include() и ThenInclude() . В следующем примере мы собираемся вернуть только одного учащегося со всеми соответствующими оценками, чтобы показать, как работает метод Include() :
Перед отправкой запроса на выполнение этого запроса мы должны установить библиотеку Microsoft.AspNetCore.Mvc.NewtonsoftJson и изменить класс Startup.cs :
Это защита от ошибки «Self-referencing loop» при возврате результата из нашего API (что действительно происходит в реальных проектах). Вы можете использовать объекты DTO, чтобы избежать этой ошибки.
Мы можем взглянуть в окно консоли, чтобы увидеть, как EF Core преобразует этот запрос в команду SQL:
Мы видим, что ядро EF выбирает первого студента из таблицы Student , а затем выбирает все относительные оценки.
Важно знать, что мы можем включать все сущности в наши запросы через сущность Student , потому что она имеет отношения с другими сущностями. Вот почему у нас есть только одно свойство DbSet типа DbSet в классе ApplicationContext .
Но если мы хотим написать отдельный запрос для других сущностей, например, Evaluation, мы должны добавить дополнительное свойство DbSet<Evaluation>
ThenInclude
Чтобы дополнительно изменить наш запрос для включения свойств отношения второго уровня, мы можем присоединить метод ThenInclude сразу после метода Include . Итак, с помощью метода Include мы загружаем свойства отношения первого уровня, и как только мы присоединяем ThenInclude , мы можем еще глубже погрузиться в граф отношений.
Имея это в виду, давайте дополнительно включим все предметы для выбранного ученика:
Сущность Student не имеет прямого свойства навигации для сущности Subject , поэтому мы включаем свойство навигации первого уровня StudentSubjects , а затем включите свойство навигации второго уровня Subject :
Мы можем пойти на любую глубину с помощью метода ThenInclude , потому что, если связь не существует, запрос не завершится ошибкой, он просто ничего не возвращает. Это также относится к методу Include .
Преимущества и недостатки быстрой загрузки и предупреждения консоли
Преимущество этого подхода заключается в том, что EF Core включает реляционные данные с помощью Include или ThenInclude эффективным способом, используя минимум доступа к базе данных (обходы базы данных).
Обратной стороной этого подхода является то, что он всегда загружает все данные, даже если некоторые из них нам не нужны.
Как мы видели, когда мы выполняем наш запрос, EF Core записывает переведенный запрос в окно консоли. Это отличная функция отладки, предоставляемая EF Core, потому что мы всегда можем решить, создали ли мы оптимальный запрос в нашем приложении, просто взглянув на переведенный результат.
Явная загрузка в Entity Framework Core
При таком подходе Entity Framework Core явно загружает отношения в уже загруженную сущность. Итак, давайте рассмотрим различные способы явной загрузки отношений:
В этом примере мы сначала загружаем объект Student . Затем мы включаем все оценки, связанные с выбранным учеником. Кроме того, мы включаем все связанные темы через свойство навигации StudentSubjects .
Важно отметить, что когда мы хотим включить коллекцию в основную сущность, мы должны использовать метод Collection , но когда мы включаем отдельную сущность в качестве свойства навигации, мы получаем использовать метод Reference .
Запросы в Entity Framework Core с явной загрузкой
При работе с явной загрузкой в Entity Framework Core у нас есть дополнительная команда. Это позволяет применить запрос к отношению. Итак, вместо использования метода Load , как мы делали в предыдущем примере, мы собираемся использовать метод Query :
Преимущество явной загрузки в том, что мы можем загрузить связь в класс сущности позже, когда она нам действительно понадобится. Еще одно преимущество заключается в том, что мы можем отдельно загружать отношения, если у нас сложная бизнес-логика. Загрузка отношений может быть перенесена в другой метод или даже класс, что упростит чтение и сопровождение кода.
Обратной стороной этого подхода является то, что у нас есть больше обращений к базе данных для загрузки всех требуемых отношений. Таким образом, запрос становится менее эффективным.
Select загрузка (проекция)
Этот подход использует метод Select для выбора только тех свойств, которые нам нужны в нашем результате. Давайте посмотрим на следующий пример:
Таким образом мы проецируем только те данные, которые хотим вернуть в ответ. Конечно, нам не нужно возвращать анонимный объект, как здесь. Мы можем создать наш собственный объект DTO и заполнить его в запросе проекции.
Преимущество этого подхода в том, что мы можем выбирать данные, которые хотим загрузить, но недостатком является то, что нам нужно писать код для каждого свойства, которое мы хотим включить в результат.
Ленивая загрузка в Entity Framework Core
Ленивая загрузка была введена в EF Core 2.1, и мы можем использовать ее, чтобы отложить извлечение данных из базы данных до тех пор, пока они действительно не понадобятся. Эта функция может помочь в некоторых ситуациях, но она также может снизить производительность нашего приложения, и это основная причина, по которой она стала дополнительной функцией в EF Core 2.1.
Оценка клиента и сервера
Все написанные нами запросы - это те запросы, которые EF Core может преобразовывать в команды SQL (как мы видели из окна консоли). Но в EF Core есть функция под названием Client vs Server Evaluation, которая позволяет нам включать в наш запрос методы, которые нельзя преобразовать в команды SQL. Эти команды будут выполнены, как только данные будут извлечены из базы данных.
Например, представим, что мы хотим показать одного учащегося с оценочными пояснениями в виде единой строки:
Начиная с EF Core 3.0 оценка клиента ограничивается только проекцией верхнего уровня (по сути, последним вызовом Select() ).
И вот результат запроса:
Несмотря на то, что оценка клиента и сервера позволяет нам писать сложные запросы, мы должны обращать внимание на количество строк, которые мы возвращаем из базы данных. Если мы вернем 20 000 строк, наш метод будет выполняться для каждой строки на клиенте. В некоторых случаях это может занять много времени.
Необработанные команды SQL
В EF Core есть методы, которые можно использовать для написания необработанных команд SQL для извлечения данных из базы данных. Эти методы очень полезны, когда:
- мы не можем создавать наши запросы стандартными методами LINQ.
- если мы хотим вызвать хранимую процедуру
- если переведенный запрос LINQ не так эффективен, как хотелось бы.
Метод FromSqlRaw
Мы также можем вызывать хранимые процедуры из базы данных:
Метод FromSqlRaw - очень полезный метод, но он имеет некоторые ограничения:
- Имена столбцов в нашем результате должны совпадать с именами столбцов, которым сопоставлены свойства.
- Наш запрос должен возвращать данные для всех свойств объекта или типа запроса.
- SQL-запрос не может содержать отношения, но мы всегда можем комбинировать FromSqlRaw с методом Include
Итак, если мы хотим включить отношения в наш запрос, мы можем сделать это следующим образом:
Метод ExecuteSqlRaw
Метод ExecuteSqlRaw позволяет нам выполнять команды SQL, такие как Update, Insert, Delete. Давайте посмотрим, как мы можем это использовать:
Эта команда выполняет требуемую команду и возвращает количество затронутых строк. Это работает одинаково при обновлении, вставке или удалении строк из базы данных. В этом примере ExecuteSqlRaw вернет 1 в качестве результата, потому что обновлена только одна строка:
Очень важно отметить, что мы используем свойство Database для вызова этого метода, тогда как в предыдущем примере нам приходилось использовать свойство Student для FromSqlRaw метод.
Еще одна важная вещь, на которую следует обратить внимание: мы используем функцию интерполяции строк для запросов в методах FromSqlRaw и ExecuteSqlRaw , поскольку она позволяет нам чтобы поместить имя переменной в строку запроса, которую EF Core затем проверяет и преобразует в параметры. Эти параметры будут проверены, чтобы предотвратить атаки SQL-инъекций. Мы не должны использовать интерполяцию строк вне методов необработанных запросов EF Core, потому что в этом случае мы потеряем обнаружение атак Sql-инъекций.
Метод перезагрузки
Если у нас есть уже загруженная сущность, а затем мы используем метод ExecuteSqlRaw для внесения некоторых изменений в эту сущность в базе данных, наша загруженная сущность наверняка будет устаревшей. Давайте изменим наш предыдущий пример:
Как только мы выполним этот запрос, столбец Age изменится на 28, но давайте посмотрим, что произойдет с загруженным объектом studentForUpdate :
Вот оно, свойство Возраст не изменилось, хотя оно было изменено в базе данных. Конечно, это ожидаемое поведение.
Итак, теперь возникает вопрос: «Что, если мы хотим, чтобы это изменилось после выполнения метода ExecuteSqlRaw?».
Что ж, для этого нам нужно использовать метод Reload :
Теперь, когда мы снова выполняем код:
Свойство age загруженного объекта изменено.
Заключение
Мы отлично поработали. Мы рассмотрели множество тем и много узнали о запросах в Entity Framework Core.
Итак, подводя итог, мы узнали:
- Как работают запросы в EF Core
- О разных типах запросов и о том, как использовать каждый из них.
- Способ использования команд Raw SQL с различными методами EF Core
В следующей статье мы узнаем о EF Core методах, которые изменяют данные в БД.
Entity Framework загрузка связанных сущностей - отложенная загрузка
Entity Framework предоставляет три метода загрузки связанных сущностей: отложенная загрузка, нетерпеливая загрузка и явная загрузка. Во-первых, давайте взглянем на определение MSDN трех методов загрузки сущностей.
Lazy Loading: Для этого типа загрузки при доступе к свойствам навигации связанные сущности автоматически загружаются из источника данных. При использовании этого типа загрузки обратите внимание, что если объект еще не находится в ObjectContext, каждое свойство навигации, к которому вы обращаетесь, приведет к выполнению отдельного запроса к источнику данных.
Eager Loading: Когда вы понимаете точную форму графики связанных сущностей, требуемых приложением, вы можете использовать метод Include ObjectQuery для определения пути запроса. Этот путь запроса контролирует, какие связанные сущности возвращаются как часть начального запроса. При определении пути запроса требуется только один запрос к базе данных для возврата всех сущностей, определенных путем запроса, в одном наборе результатов, и все связанные сущности, принадлежащие к типу, определенному в пути, будут загружены с каждым объектом, возвращаемым запрос.
Explicit Loading: Для явной загрузки сущности в ObjectContext требуется несколько циклов обращения к базе данных и может потребоваться несколько активных наборов результатов, но объем возвращаемых данных ограничен загруженной сущностью. Вы можете использовать метод Load для EntityCollection или EntityReference или метод LoadProperty для ObjectContext для явного извлечения связанных сущностей из источника данных. Для каждого вызова метода Load открывается соединение с базой данных для получения связанной информации. Это гарантирует, что запрос никогда не будет выполнен, если нет явного запроса для связанной сущности.
Давайте протестируем указанные выше три метода загрузки один за другим.
Перед тестированием мы сначала создаем тестовую базу данных и вставляем в нее некоторые данные:
Lazy Loading
В Entity Framework 4.0 и более поздних версиях LazyLoading включена по умолчанию. После создания модели из базы данных мы можем щелкнуть пустое пространство файла EDMX и увидеть этот параметр в окне свойств:
Вы также можете открыть файл EDMX в форме XML и увидеть этот параметр в разделе CSDL:
Примечание: Настройка отложенной загрузки предназначена для всех моделей, а не для определенной модели.
Затем мы пишем простой код для тестирования отложенной загрузки:
Результат после бега выглядит следующим образом:
Мы видим, что в операторе запроса мы просим вернуть только всю информацию о команде, и нет никакой информации о загрузке игроков, такой как запрос базы данных, но в операторе Foreach мы просим распечатать количество игроков для каждой команды, но это удается. Это эффект, достигнутый с помощью отложенной загрузки. Фактически, когда выполняется оператор Count, программа извлекает базу данных запроса и возвращает информацию об игроке, что означает, что если у нас есть 100 команд, программа будет обращаться к базе данных 100 раз для выполнения этой операции.
Отключим отложенную загрузку, чтобы увидеть эффект. Есть много способов отключить отложенную загрузку. Мы можем напрямую установить для Lazy Loading Enabled значение False в окне свойств на рисунке 2 или установить для Lazy Loading Enabled значение False в коде XML. Ниже мы используем программный код для отключения Lazy. Загрузка и выполнение приведенного выше кода, чтобы увидеть эффект:
Результаты приведены ниже:
Из результатов выполнения видно, что при выполнении оператора Foreach программа не запрашивает базу данных, а наш оператор запроса не запрашивает информацию об игроках из базы данных, поэтому количество игроков не может быть напечатано.
Наконец, давайте суммируем преимущества и недостатки ленивой загрузки: когда ленивая загрузка включена, нам не нужно заботиться о том, была ли загружена сущность, и она не будет отображаться при вызове сущности, и появляется смущение null, экономия большого количества программистов. В то же время недостатки очень очевидны. Если у нас есть большое количество сущностей и часто вызываются связанные сущности, программа будет часто обращаться к базе данных, что, очевидно, будет влиять на производительность программы.
В следующий раз мы проанализируем, как отображать связанные с загрузкой сущности, то есть явную загрузку, когда отложенная загрузка отключена.
Читайте также: