Entity Framework извлекает данные из таблицы и создает запросы

Entity Framework извлекает данные из таблицы и создает запросы ФСС

Отложенная загрузка (lazy loading)

Отложенная загрузка (lazy loading) заключается в том, что Entity Framework автоматически загружает данные, при этом не загружая связанные данные. Когда потребуются связанные данные Entity Framework создаст еще один запрос к базе данных. В контексте нашего примера это означает, что вы можете, например, загрузить первого заказчика из таблицы Customers и сохранить его в переменной customer. Затем вам может понадобиться узнать, какие заказы связаны с этим покупателем. Напомню, в классе модели Customer у нас определено навигационное свойство Orders. Если вы обратитесь к этому свойству (customer. Orders), то Entity Framework отправит запрос в базу данных на извлечение всех связанных с этим покупателем заказов.

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

DbContext имеет настройку конфигурации для отложенной загрузки с помощью свойства DbContext. Configuration. LazyLoadingEnabled. Этот параметр включен по умолчанию, поэтому если вы не изменяли значение по умолчанию для него, динамический прокси-объект будет выполнять отложенную загрузку.

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

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

Если вы теперь вызовите этот метод в главном методе Main(), то никакие связанные данные не будут загружаться (т.к. мы еще не изучили как вставлять данные с помощью EF, вам нужно будет вручную вставить какие-нибудь данные в таблицу Orders с помощью Visual Studio или SQL Server Management Studio, для тестирования последующих примеров). В этом примере не происходит отложенная загрузка, т.к. наш класс модели Customer не подходит под второе условие – навигационное свойство Customer. Orders не является виртуальным. Давайте изменим это:

Теперь, при запуске приложения Entity Framework создаст динамически прокси-объект для класса Customer и извлечет данные заказов из базы при их запросе. На рисунке ниже наглядно показано какие данные загружает Entity Framework в этом примере:


Entity Framework извлекает данные из таблицы и создает запросы

Отложенная загрузка является очень простой в использовании, потому что используется автоматически. При этом, она является довольно опасной в плане производительности! Что если мы захотим извлечь сначала всех пользователей используя не отложенный запрос, а затем захотим извлечь все заказы, связанные с этими пользователями:

В этом примере создается три запроса, при попытке извлечь все заказы, связанные с заказчиками в коллекции customers. Мы даже включили средство протоколирования в этом запросе, чтобы вы убедились сами. Количество запросов SELECT при извлечении данных из таблицы Orders зависит от количества покупателей в коллекции customers – Entity Framework будет отправлять один запрос на выборку заказов для каждого покупателя. Очевидно, что такой подход является катастрофическим в плане производительности, если в коллекции будет храниться большое число покупателей.

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

Последнее обновление: 21.11.2021

Кроме использования инфраструктуры LINQ to Entities для создания запросов Entity Framework Core также позволяет писать запросы к базе данных
сразу на языке SQL. Это может быть удобно, если запрос очень сложный по своей структуре или если Entity Framework Core на основе Linq to Entities создает
не очень оптимальный sql-запрос.

FromSqlRaw

Для получения данных из БД у объектов DbSet определен метод , который принимает в качестве параметра sql-выражение и набор параметров.
В качестве результата FromSqlRaw возвращает набор полученных из бд объектов.

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

Для примера возьмем следующие модели и контекст данных:

Пусть ранее у нас были добавлены следующие данные:

Например, получим все объекты из таблицы Companies:

Выражение SELECT извлекает данные из таблицы. Так как эта таблица сопоставляется с моделью Company и хранит объекты этой модели, то
в результате мы получим набор объектов класса Company.

При этом мы можем добавлять к методу другие методы LINQ, которые все вместе будут конкатенироваться в одно общее SQL-выражение:

В итоге будет выполняться следующее SQL-выражение:

SELECT «c».»Id», «c».»Name»
FROM (
SELECT * FROM Companies
) AS «c»
ORDER BY «c».»Name»

Также можно использовать метод Include для подгрузки связанных данных:

Параметры

Другая версия метода FromSqlRaw() позволяет использовать параметры. Например, выберем из бд все модели, в названии которых есть подстрока «Tom»:

Читайте также:  Фонд социального страхования индекс и НД

Если бы подключение шло к бд MS SQL Server, то для представления параметров применялся бы класс из пространства
имен :

Также мы можем определять параметры как простые переменные:

ExecuteSqlRaw

Метод FromSqlRaw() осуществляет выборку из БД, но кроме выборки нам, возможно, придется удалять, обновлять уже существующие или вставлять
новые записи. Для этой цели применяется метод и его асинхронная версия — , которые возвращают количество затронутых командой строк.
Для его вызова у контекста данных используется свойство Database:

Интерполяция строк

Для методов FromSqlRaw и ExecuteSqlRaw в EF Core определены их двойники — методы и
(асинхронная версия — ), которые позволяют использовать интерполяцию строк для передачи параметров. Пример с
:

В этом случае на стороне сервера также будут создаваться параметры для передачи значений name и age наподобие:

Исходный проект

В качестве примера проекта, на котором мы будем тестировать наши запросы, мы будем использовать проект консольного приложения C#. Ранее, мы использовали для демонстрации примеров веб-приложение ASP. NET, которое было создано в статье “Использование Code-First”. Очевидно, что при рассмотрении примеров работы с данными проект веб-приложения будет громоздким, т.к. для каждого примера нужно будет редактировать разметку веб формы. Поэтому создайте новый проект, используя шаблон Console Application, как показано на рисунке ниже:


Entity Framework извлекает данные из таблицы и создает запросы

Entity Framework извлекает данные из таблицы и создает запросы

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

Здесь используется простая модель с двумя классами, описывающими покупателя и его заказы. Мы использовали эту модель при обсуждении настроек классов с помощью Fluent API и аннотаций данных при рассмотрении подхода Code-First на протяжении нескольких предыдущих статей.

Стоит отметить, что мы будем использовать подход Code-First при рассмотрении работы с данными в Entity Framework, хотя эта работа не зависит от конкретного подхода. При любом подходе модель данных использует класс контекста, унаследованный от DbContext, соответственно запросы для использования данных будут одинаковыми. Я использую подход Code-First, т.к. он обеспечивает настройку структуры базы данных из управляемого кода C#. Вы можете использовать подходы Model-First или Database-First при работе с примерами.

Далее необходимо будет добавить класс контекста, связывающий сущностные классы с базой данных. Для этого добавьте новый файл SampleContext.cs в папку Model, имеющий следующее содержимое:

После этого вы можете добавить следующий код в программу:

В этом примере добавляется новый покупатель в таблицу Customers. Теперь, если вы запустите программу Entity Framework создаст базу данных MyShop на локальном сервере SQLEXPRESS (если эта база данных еще не была создана). В последующих статьях мы будем показывать примеры, которые используются в этом консольном приложении. Далее мы опишем некоторые особенности запросов, которые используются в Entity Framework.

Привет!

Сегодня мы немного поговорим про EntityFramework. Совсем чуть-чуть. Да, я знаю что к нему можно относиться по-разному, многие от него плюются, но за неимением лучшей альтернативы — продолжают использовать.

Что мы больше всего любим в прямом SQL? Скорость и простоту. Там, где «в лучших традициях ORM» надо выгрузить в память вагончик объектов и всем сделать context. Remove (ну или поманипулировать Attach-ем), можнo обойтись одним мааааленьким SQL-запросом.
Что мы больше всего не любим в прямом SQL? Правильно. Отсутствие типизации и взрывоопасность. Прямой SQL обычно делается через DbContext. Database. ExecuteSqlCommand, а оно на вход принимает только строку. Следовательно, Find Usages в студии никогда не покажет вам какие поля каких сущностей ваш прямой SQL затронул, ну и помимо прочего вам приходится полагаться на свою память в вопросе точных имён всех таблиц/колонок которые вы щупаете. А ещё молиться, что никакой лоботряс не покопается в вашей модели и не переименует всё в ходе рефакторинга или средствами EntityFramework, пока вы будете спать.

Так ликуйте же, адепты маленьких raw SQL-запросов! В этой статье я покажу вам как совместить их с EF, не потерять в майнтайнабильности и не наплодить детонаторов. Ныряйте же под кат скорее!

А чего конкретно хотим достичь?

Итак, в этой статье я покажу вам отличный подход, который раз и навсегда избавит вас от беспокойства о проблемах, которые обычно вызывает прямой SQL в тандеме с EntityFramework. Ваши запросы приобретут человеческий облик, будут находиться через Find Usages и станут устойчивы к рефакторингу (удалению/переименованию полей в сущностях), а ваши ноги потеплеют, язвы рассосутся, карма очистится.

Нам понадобится: C# 6.0 (ну, тот, в котором интерполяция строк реализована), лямбда-выражения и немножко прямых рук. Я назвал эту технику «SQL Stroke». В конечном счете мы напишем несколько extension-методов для DbContext, позволяющих отправлять в базу SQL со строго типизированными вставками. Для этого нам понадобится пообщаться с метаданными EntityFramework, попарсить лямбда-выражения и починить все возникающие по ходу баги и corner case-ы.

Вот как будет выглядеть ваш прямой SQL после прочтения этой статьи:

TL;DR: короче вот оно на гитхабе, там нагляднее

Как-то так. Теперь давайте просмакуем подробности.

Сопоставление классов и свойств с таблицами и колонками

Давайте освежим ваши знания Reflection-а: представьте что у вас есть класс (точнее Type) и у вас есть строка с именем проперти из этого класса. Так же имеется наследник EF-ного DbContext-а. Имея оные две вилки и тапок вам надобно добыть имя таблицы, на которую мапится ваш класс и имя колонки в БД, на которую мапится ваша проперть. Сразу же оговорюсь: решение этой задачи будет отличаться в EF Core, однако же на основную идею статьи это никак не влияет. Так что я предлагаю читателю самостоятельно реализовать/нагуглить решение этой задачи.

Читайте также:  Горячая линия ФСС в Москве

Итак, EF 6. Требуемое можно достать через весьма популярную магию приведения EF-ного контекста к IObjectContextAdapter:

И, пожалуйста, не спрашивайте меня что же разработчики EntityFramework курили имели в виду, создавая такие лабиринты абстракций и что в нём означает каждый закоулочек. Честно признаюсь — я сам в этом лабиринте могу заблудиться и кусок выше я, не писал, а просто нашел и распотрошил.

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

Так, и вот тут я сразу и крупными буквами предупреждаю читателя: копаться в EF-метаданных — это медленно! Кроме шуток. Поэтому кэшируйте вообще всё, до чего дотянетесь. В статье есть ссылка на мой код — там я уже озаботился кэшированием — можете пользоваться. Но все равно держите в голове: реальные концептуальные модели EF — стозёвные чудища, хранящие в себе взводы и дивизии различных объектов. Если вам нужно только соотношение тип-имя таблицы и тип/свойство — имя колонки, то лучше один раз достаньте и закэшируйте (только не напоритесь там на утечку памяти — не храните ничего от DbContext-а). В EF Core, говорят, с этим по-лучше.

Выражения

Самое скучное позади. Теперь — лямбда-выражения. Положим, мы хотим иметь метод Stroke, чтобы вызывать его можно было вот таким макаром:

Сам метод Stroke простой:

Таким образом, что мы сейчас сделаем в методе Parse? Мы клещами вытянем оригинальную строку формата, а аргументы форматирования пересчитаем, заменяя где надо на имена таблиц и колонок. После чего сами вызовем Format, чем и соберем оригинальную строку формата и обработанные аргументы в результирующую SQL-строку. Честное слово, это гораздо проще закодить чем объяснить 🙂

В первом мы просто перебираем цепочку обращений к членам, пока не дойдем до корня (я назвал его GetRootMember):

Во втором — собственно проверяем требуемые нам условия:

Готово. Возвращаемся к перебору аргументов:

Отлично. Мы отсекли все параметры, которые гарантированно не являются ссылками на наши таблицы/колонки. Список sqlParams потом вернётся через out-параметр — мы его наряду со строкой-результатом скормим context. Database. ExecuteSqlCommand вторым аргументом. Пока же обработаем ссылки на таблицы:

Тут нам придется отрезать возможность обращаться к агрегатам, ибо как это приведет к необходимости переколбашивать запрос JOIN-ами, чего мы технически сделать не можем. Так что — увы и ах. Если наш аргумент — это обращение к члену, но не к члену непосредственно параметра выражения — то звиняйте, ничем не можем помочь:

И вот, наконец, мы можем добыть наше имя колонки и добавить его в переработанный список формат-аргументов.

Теперь, когда все аргументы перебраны, мы можем наконец-таки сделать string. Format самостоятельно и получить SQL-строку и массив параметров, готовые к скармливанию ExecuteSqlCommand.

var sqlString = string. Format(format, formatArgs. ToArray());
parameters = sqlParams. ToArray();
return sqlString;
}

Готово

Вот как-то так. Для статьи я намеренно упростил код. В частности, полная версия автоматически подставляет алиасы таблиц, нормально кэширует имена таблиц и колонок, а так же содержит перегрузки . Stroke до 8 параметров. С полным исходным кодом вы можете ознакомитья в моем github. За сим прощаюсь и желаю всяческих удач в разработке.

А, ну и опросик напоследок:

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Как часто вы используете прямой SQL, даже если подключен ORM?

Никогда. Строго иду по ОО-пути и не сворачиваю

Время от времени бывает

Постоянно сталкиваюсь с такой необходимостью

Проголосовали 127 пользователей.

Воздержались 34 пользователя.

Использование запросов Entity SQL

Запросы Entity SQL используют SQL-подобный синтаксис, при этом взаимодействие с базой данных происходит с помощью вызова SQL-команд. Фактически такой подход к созданию запросов в Entity Framework идентичен использованию ADO. NET, поэтому в последующих примерах мы не будем никогда его использовать. Но давайте рассмотрим простой пример использования такого запроса в Entity Framework:

Как видите, Entity SQL напрямую не поддерживается в современных версиях Entity Framework, т.к. нам нужно использовать старый класс контекста ObjectContext. Мы получили экземпляр этого класса из объекта SampleContext, путем приведения его к интерфейсу IObjectContextAdapter. Затем мы использовали SQL-команды для загрузки данных из таблицы базы данных. Очевидно, что данный подход не использует одно из главных преимуществ Entity Framework – использование объектной модели базы данных, поэтому мы не будем его использовать далее.

Стоит отметить, что в DbContext API есть способ выполнять произвольные SQL-инструкции. Для этого служит метод ExecuteSqlCommand() класса Database, объект которого доступен через одноименное свойство класса контекста. Но при этом, этот метод был создан для того, чтобы вы могли выполнить произвольную SQL-инструкцию для обращения к базе данных (например, вы можете изменить кодировку таблицы или добавить триггер), но этот метод не стоит использовать для извлечения или работы с данными.

Создание запросов

Работа с базами данных в . NET Framework — Entity Framework 6 — Создание запросов

В приложениях, работающих с базами данными, возникает две задачи при работе с данными: как получить данные из базы данных и как сохранить изменения. В Entity Framework работа с данными строится на создании запросов к базе данных, используя средства языка C# (LINQ) и специальные методы класса контекста DbContext. В этой статье мы кратко рассмотрим синтаксические особенности запросов, а в следующих статьях увидим, как с помощью запросов можно извлекать, сохранять, изменять и удалять данные в базе.

Использование LINQ

Базовым подходом к написанию запросов в Entity Framework является использование расширения языка C# — LINQ, которое предоставляет набор методов для работы с коллекциями. Подход с использованием LINQ в Entity Framework мы рассматривали в разделе LINQ to Entities, при этом этот раздел был написан еще до появления версии EF 4.1, поэтому там мы использовали класс контекста ObjectContext. Далее мы будем рассматривать запросы LINQ to Entities при использовании класса контекста, унаследованного от DbContext.

Читайте также:  Выплаты, производимые ФСС РФ - Государственное учреждение - Ростовское региональное отделение Фонда социального страхования Российской Федерации

LINQ тесно интегрирован с различными шаблонами программирования . NET и обеспечивает строго типизированный язык запросов для обращения к объектам модели. Запрос определяется с помощью классов и их свойств, которые составляют модель (в отличие от Entity SQL, где мы использовали запросы SQL). Это обеспечивает ряд преимуществ:

Стоит отметить, что расширение LINQ не является чем-то специфическим для Entity Framework и может повсеместно использоваться в коде приложения для работы с коллекциями. В контексте Entity Framework, коллекции являются множеством строк таблицы, с которыми можно работать посредством LINQ.
Давайте реализуем функциональность предыдущего примера с использованием LINQ-запроса:

Этот пример гораздо проще и понятней, чем тот, что мы использовали при демонстрации Entity SQL. Здесь мы использовали метод расширения LINQ – FirstOrDefault(), который выбирает первую запись из коллекции (она же таблица, в понимании EF) Customers. Стоит отметить, что базовые методы расширения LINQ находятся в пространстве имен System. Linq, при этом Entity Framework также предлагает некоторые методы расширения, которые находятся в пространстве имен System. Data. Entity.

Стоит отметить также, что LINQ поддерживает использования синтаксиса SQL в запросах (синтаксис с вызовом цепочки методов, как в предыдущем примере, называется синтаксисом точечной нотации). Эта возможность удобна для программистов, тесно работающих с базами данных и хорошо знающих языки запросов, например T-SQL. Ниже показан пример использования синтаксиса SQL, которые работает также, как и показанные выше примеры:

string name = (from customer in context. Customers
select customer. FirstName)
. FirstOrDefault();

Console. WriteLine(name);

Стоит отметить что этот синтаксис довольно ограничен, поэтому в этом примере нам пришлось вызвать метод FirstOrDefault() с использованием синтаксиса точечной нотации, т.к. для этого метода не определен соответствующий псевдоним.

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

После запуска этого примера, в консоль будет выведена SQL-команда для этого запроса. Также, SQL-запрос можно вывести, например, в окно отладчика среды Visual Studio:

Класс System. Diagnostics. Debug как раз является средством взаимодействия между кодом и отладчиком Visual Studio. Сгенерированный код отображается на панели Output:


Entity Framework извлекает данные из таблицы и создает запросы

Обратите внимание, если вы прокрутите это окно выше, то увидите что Entity Framework направил еще один запрос базе данных, для извлечения данных таблицы __MigrationHistory. Как объяснялось в статье “Миграции модели в Code-First”, эта таблица служит для отслеживания изменений в модели данных. Если вас мучают вопросы производительности приложений, то вы можете не волноваться на счет этого момента, так как запрос к таблице с миграциями выполняется один раз, при запуске приложения.

Отложенная компиляция LINQ-запросов

Как описывалось только что, LINQ компилирует запросы из управляемого кода в SQL-инструкции. При этом, если мы удалим вызов метода FirstOrDefault() в примере выше, сам запрос к базе данных будет выполняется не при инициализации переменной name, а при ее вызове в методе Console. WriteLine (в этом случае будет возвращаться коллекция имен пользователей из таблицы). Такой подход называется отложенным выполнением LINQ-запросов, т.е. запрос компилируется не при его объявлении, а при непосредственном вызове переменной, содержащей этот запрос, в коде.

Это обеспечивается благодаря тому, что класс DbSet, экземпляр которого мы получаем через свойство context. Customers, реализует интерфейс IQueryable. Этот интерфейс специфичен для LINQ и находится в пространстве имен System. Linq. Он является производным от интерфейса коллекций IEnumerable и обеспечивает отложенное выполнение запросов. В разделе LINQ to Objects вы можете увидеть, какие методы LINQ выполняют отложенные запросы, а какие методы вызывают запрос сразу при его объявлении. Метод FirstOrDefault() относится к неотложенным запросам, поэтому, в предыдущем примере запрос будет выполняться при инициализации переменной name.

Чтобы быстро понять концепцию отложенных запросов LINQ, достаточно просто взглянуть на следующий рисунок:


Entity Framework извлекает данные из таблицы и создает запросы

В первом примере запрос выполняется при первой итерации цикла foreach, что является стандартным поведением отложенных запросов LINQ. Во втором примере мы использовали не отложенный метод ToList(), для выполнения запроса при объявлении переменной names. Понимание отложенной природы запросов в LINQ является важным при работе с Entity Framework, т.к. выполнение запроса в коде в Entity Framework означает, что мы выполняем запрос к базе данных.

Загрузка связанных данных

Работа с базами данных в . NET Framework — Entity Framework 6 — Загрузка связанных данных

До сих пор мы загружали данные только из одной таблицы базы данных. Но в реальном приложении, скорее всего будут использоваться несколько таблиц с отношениями (связями) между друг другом. В примере нашего приложения существуют две связанные таблицы – Customers и Orders, первая содержит данные покупателей, а вторая их заказы. Возможно нам нужно загрузить данные какого-то покупателя и все связанные с ним заказы.

В Entity Framework существует три подхода для загрузки связанных данных: “отложенная загрузка” (lazy loading), “прямая загрузка” (eager loading) и “явная загрузка” (explicit loading). С помощью этих подходов обеспечивается одинаковая загрузка данных, но при этом они влияют на производительность приложения. В последующих разделах мы опишем каждый из этих подходов.

Оцените статью
ФСС Help
Добавить комментарий