CRUD операции со связанными данными.

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

Большинство операций с данными так или иначе представляют собой CRUD операции (Create, Read, Update, Delete), то есть создание, получение, обновление и удаление. Entity Framework Core позволяет легко выполнять все эти действия.

Для примера создадим проект по типу Console App (.NET Core) . И после создания проекта сразу добавим в него функциональность EF Core. Для этого в проект через NuGet пакеты Microsoft.EntityFrameworkCore.SqlServer и Microsoft.EntityFrameworkCore.Tools .

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

Public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }

И добавим класс контекста данных ApplicationContext:

Using Microsoft.EntityFrameworkCore; namespace HelloApp { public class ApplicationContext: DbContext { public DbSet Users { get; set; } public ApplicationContext() { Database.EnsureCreated(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;"); } } }

Using System; using System.Linq; namespace HelloApp { public class Program { public static void Main(string args) { // Добавление using (ApplicationContext db = new ApplicationContext()) { User user1 = new User { Name = "Tom", Age = 33 }; User user2 = new User { Name = "Alice", Age = 26 }; // Добавление db.Users.Add(user1); db.Users.Add(user2); db.SaveChanges(); } // получение using (ApplicationContext db = new ApplicationContext()) { // получаем объекты из бд и выводим на консоль var users = db.Users.ToList(); Console.WriteLine("Данные после добавления:"); foreach (User u in users) { Console.WriteLine($"{u.Id}.{u.Name} - {u.Age}"); } } // Редактирование using (ApplicationContext db = new ApplicationContext()) { // получаем первый объект User user = db.Users.FirstOrDefault(); if(user!=null) { user.Name = "Bob"; user.Age = 44; //обновляем объект //db.Users.Update(user); db.SaveChanges(); } // выводим данные после обновления Console.WriteLine("\nДанные после редактирования:"); var users = db.Users.ToList(); foreach (User u in users) { Console.WriteLine($"{u.Id}.{u.Name} - {u.Age}"); } } // Удаление using (ApplicationContext db = new ApplicationContext()) { // получаем первый объект User user = db.Users.FirstOrDefault(); if (user != null) { //удаляем объект db.Users.Remove(user); db.SaveChanges(); } // выводим данные после обновления Console.WriteLine("\nДанные после удаления:"); var users = db.Users.ToList(); foreach (User u in users) { Console.WriteLine($"{u.Id}.{u.Name} - {u.Age}"); } } Console.Read(); } } }

И после выполнения мы получим следующий консольный вывод:

Данные после добавления 1.Tom - 33 2.Alice - 26 Данные после редактирования 1.Bob - 44 2.Alice - 26 Данные после удаления 2.Alice - 26

Добавление

Для добавления объекта используется метод Add , определенный у класса DbSet, в который передается добавляемый объект:

Db.Users.Add(user2); db.SaveChanges();

Метод Add устанавливает значение Added в качестве состояния нового объекта. Поэтому метод db.SaveChanges() сгенерирует выражение INSERT для вставки модели в таблицу.

Если нам надо добавить сразу несколько объектов, то мы можем воспользоваться методом AddRange() :

User user1 = new User { Name = "Tom", Age = 33 }; User user2 = new User { Name = "Alice", Age = 26 }; db.Users.AddRange(user1, user2);

Удаление

Удаление производится с помощью метода Remove :

Db.Users.Remove(user); db.SaveChanges();

Данный метод установит статус объекта в Deleted, благодаря чему Entity Framework при выполнении метода db.SaveChanges() сгенерирует SQL-выражение DELETE.

Если необходимо удалить сразу несколько объектов, то можно использовать метод RemoveRange() :

User user1 = db.Users.FirstOrDefault(); User user2 = db.Users.LastOrDefault(); db.Users.RemoveRange(user1, user2);

Редактирование

При изменении объекта Entity Framework сам отслеживает все изменения, и когда вызывается метод SaveChanges() , будет сформировано SQL-выражение UPDATE для данного объекта, которое обновит объект в базе данных.

Но надо отметить, что в данном случае действие контекста данных ограничивается пределами конструкции using. Но рассмотрим другой пример. Мы получаем объект в одном месте,а обновляем в другом. Например:

User user = null; using (ApplicationContext db = new ApplicationContext()) { // получаем объект user = db.Users.FirstOrDefault(); Console.WriteLine("Данные до редактирования:"); var users = db.Users.ToList(); foreach (User u in users) { Console.WriteLine($"{u.Id}.{u.Name} - {u.Age}"); } } //................... // Редактирование using (ApplicationContext db = new ApplicationContext()) { // Редактирование if (user != null) { user.Name = "Sam"; user.Age = 33; } db.SaveChanges(); // выводим данные после обновления Console.WriteLine("\nДанные после редактирования:"); var users = db.Users.ToList(); foreach (var u in users) { Console.WriteLine($"{u.Id}.{u.Name} - {u.Age}"); } }

Несмотря на то, что объект user не равен null, имеется в базе данных, но во втором блоке using обновления соответствующего объекта в БД не произойдет. И в этом случае нам надо использовать метод Update:

// Редактирование using (ApplicationContext db = new ApplicationContext()) { // Редактирование if (user != null) { user.Name = "Sam"; user.Age = 33; db.Users.Update(user); } db.SaveChanges(); // выводим данные после обновления Console.WriteLine("\nДанные после редактирования:"); var users = db.Users.ToList(); foreach (var u in users) { Console.WriteLine($"{u.Id}.{u.Name} - {u.Age}"); } }

При необходимости обновить одновременно несколько объектов, применяется метод UpdateRange() :

Db.Users.UpdateRange(user1, user2);

I am a new SQL Server DBA, I heard of the CRUD acronym, but I do not quite understand the meaning and importance of these CRUD operations, can you please give a detailed explanation?

Solution

CRUD is the basic operations in any RDBMS, and this tip will take a detailed look at the CRUD operations in SQL Server.

What is CRUD?

CRUD means Create, Read, Update, Delete, and it can mean different things in different systems, but for SQL Server, it is commonly considered to map to the following SQL operations on table records.

CRUD SQL
C - Create Insert
R - Read Select
U - Update Update
D - Delete Delete

Here are two examples

In SQL Server 2008, there is a MERGE statement which can achieve the functions of CUD (no R). I purposely omit it here as it is not available across all SQL Server versions and also it is impossible to classify it to C or U or D.

Extending CRUD Concept

CRUD in essence seems to be DML (data manipulation language), but this concept can be extended to DDL (Data Definition Language). For example, if we consider a database as a container, we can CRUD lots of database objects, such as table, view, stored procedure and user, etc. Let’s use a table as an object, we can see the following CRUD actions.

Yii – это высокопроизводительный фреймворк, который работает быстро, безопасно и хорошо подходит для приложений Web 2.0.

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

К тому же, фреймворк предлагает много удобных, уже готовых функций, таких как: скаффолдинг, объекты доступа к данным, тематизация, контроль доступа, кэширование и многое другое. В этой статье я расскажу основы того, как используя Yii, создать CRUD систему.

Приступим

Я исхожу из того, что у вас уже установлены и скачайте последнюю стабильную версию (на момент написания статьи – версия 1.1.13).

Распакуйте ZIP-архив, чтобы получить папку yii-1.1.13.e9e4a0 (идентификатор версии может отличаться в зависимости от версии, которую вы загрузили), переименуйте папку в yii, затем поместите ее в ваш корневой каталог, доступный из сети.

В моем случае, это C:wampwww таким образом, путь к файлам фреймворка будет следующим: C:wampwwwyii .

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

Затем мы должны проверить, какие функции Yii будут поддерживаться нашей системой. Откройте ссылку http://localhost/yii/requirements в вашем браузере, чтобы увидеть детали требований фреймворка.

Поскольку мы будем работать с базой данных MySQL, расширение MYSQL PDO должно быть разрешено.

Мы хотели быстро проверить требования Yii, и поэтому поместили файлы в доступный каталог, но рекомендуется хранить файлы Yii вне сетевого каталога. После проверки можете свободно переместить файлы Yii куда угодно.

Двигаемся дальше

Каждое веб-приложение имеет свою структуру каталога, и Yii приложения также нуждаются в поддержке иерархичной структуры внутри сетевого каталога.

Чтобы создать каркас приложения с подходящей структурой каталога, вы можете использовать Yii инструмент yiic, работающий из командной строки. Перейдите в сетевой каталог и введите следующее:

frameworkyiic webapp yiitest

Эта команда создаст скелет приложения под названием yiitest с минимальным набором необходимых файлов. Внутри вы найдете файл index.php , который служит в качестве скрипта входа, он принимает пользовательские запросы и решает, какой контроллер должен обработать запрос.

Фреймворк Yii основан на принципах MVC и ООП, поэтому вы должны разбираться в этих темах. Если вы не знакомы с MVC, почитайте серию статей The MVC Pattern and PHP , которая предлагает хорошее введение в эту тему.

В Yii URL-адрес выглядит как http://localhost/yiitest/index.php?r=controllerID/actionID . Например, в блоговой системе URL может быть следующим: http://localhost/yiitest/index.php?r=post/create. post – это идентификатор контроллера, а create – идентификатор действия.

Основываясь на идентификаторах, скрипт входа решает, какой контроллер и метод вызвать.

Контроллер, имеющий идентификатор post, должен быть назван PostController (идентификатор получается путем отсечения суффикса Controller от имени класса и изменением первой буквы на строчную).

Идентификатор действия – это идентификатор метода, представленного в контроллере подобным образом; внутри PostController должен быть метод под названием actionCreate() .

Может быть несколько представлений, ассоциированных с одним контроллером, поэтому мы храним файлы представлений внутри папок protected/views/controllerID .

Мы можем создать файл представления для нашего контроллера под названием create.php в описанном выше каталоге, и затем представить его пользователям, просто написав следующий код в actionCreate():

public function actionCreate() { $this->render("create"); }

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

$this->render("create", array("data" => $data_item));

Внутри файла представления мы можем получить доступ к данным через переменную $data.

Представление также имеет доступ к переменной $this , которая указывает на экземпляр контроллера, воспроизводящего представление.

Более того, если вы хотите иметь удобные для пользователя URL-адреса, то можете раскомментировать следующий участок кода в файле protected/config/main.php :

"urlManager"=>array("urlFormat"=>"path", "rules"=>array("/"=>"/view", "//"=>"/", "/"=>"/",)

Тогда URL-адреса будут выглядеть следующим образом: http://localhost/yiitest/controllerID/actionID .

Разработка CRUD приложения

Теперь, когда вы знаете важные соглашения по Yii, пришло время приступить к CRUD. В этой части мы разработаем простую систему, в которой пользователь может выполнять CRUD операции (создание, извлечение, обновление и удаление) с сообщением в блоге.

Шаг 1

Создадим базу данных MySQL под названием yiitest и внутри нее создадим таблицу posts. Таблица будет иметь только три столбца: идентификатор (id ), название (title ) и контент (content ).

CREATE TABLE posts (id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100), content TEXT)

Откройте файл конфигурации вашего приложения (protected/config/main.php ) и раскомментируйте следующие строки:

"db"=>array("connectionString" => "mysql:host=localhost;dbname=testdrive, "emulatePrepare" => true, "username" => "root", "password" => "", "charset" => "utf8",)

Замените testdrive на имя вашей базы данных, то есть, yiitest. Также вы должны обеспечить полномочия, необходимые Yii для подключения.

Шаг 2

В фреймворке Yii каждая таблица базы данных должна иметь соответствующий класс модели типа CActiveRecord . Польза от этого в том, что нам не нужно иметь дело с таблицами баз данных напрямую. Вместо этого мы можем работать с объектами модели, которые соответствуют различным строкам таблицы.

Например, класс Post – это модель для таблицы сообщений. Объект этого класса представляет собой строку из таблицы posts и имеет атрибуты для отражения значений столбцов.

Для того чтобы быстро генерировать модель, мы будем использовать веб-инструмент Yii под названием gii . Этот инструмент может быть использован для генерации моделей, контроллеров и форм для CRUD операций.

Чтобы использовать gii в вашем проекте, найдите следующие строки в файле конфигурации вашего проекта, раскомментируйте их добавьте пароль.

"gii"=>array("class"=>"system.gii.GiiModule", "password"=>your password to access gii, "ipFilters"=>array("127.0.0.1","::1"),)

Затем обратитесь к gii с помощью следующего адреса: http://localhost/yiitest/index.php?r=gii . Если вы используете дружественные пользователю URL-адреса, адрес будет такой: http://localhost/yiitest/gii .

Кликнете на Model Generator. gii попросит вас ввести имя таблицы; введите posts для имени таблицы, а для имени модели используйте Post. Затем кликнете Generate для создания модели.

Проверьте папку protected/models, и вы найдете там файл Post.php.

Шаг 3

Теперь кликнете на CRUD Generator . Введите в качестве имени модели Post. Идентификатор контроллера автоматически заполнится как post.

Это означает, что новый контроллер будет сгенерирован под именем PostController.php .

Кликнете на Generate. Сгенерируется контроллер, а также несколько файлов представления с формами, необходимыми для CRUD операций.

Теперь у вас есть совершенно новое CRUD приложение! Кликнете на ссылку try it now , чтобы протестировать его. Для управления сообщениями вам нужно будет войти как admin/admin .

  • Перевод

Это продложение цикла статей, посвященого разработке с помощью Entity Framework и ASP.NET MVC 3. Первую главу вы можете найти по следующей ссылке: Создание модели данных Entity Framework для приложения ASP.NET MVC .

В предыдущем уроке мы создали MVC-приложение, которое умеет хранить и показывать данные с использованием Entity Framework и SQL Server Compact. В этом уроке мы рассмотрим создание и настройку CRUD (create, read, update, delete)-функциональности, которую MVC scaffolding автоматически создает для вас в контроллерах и представлениях.

Note общепринятой практикой является реализация паттерна «репозиторий» для создания слоя абстракции между контроллером и слоем доступа к данным. Но это будет потом, в поздних уроках (Implementing the Repository and Unit of Work Patterns).

В этом уроке будут созданы следующие страницы:

Создание страницы Details

На странице Details будет показываться содержимое коллекции Enrollements в HTML-таблице.

В Controllers \ StudentController . cs метод для представления Details представляет собой следующий код:

Public ViewResult Details(int id) { Student student = db.Students.Find(id); return View(student); }
Метод Find используется для извлечения одной сущности Student, соответствующей переданному в метод параметру id. Значение id берется из строки запроса находящейся на странице Details ссылки.

Откройте Views \ Student \ Details . cshtml . Каждое поле отображается хелпером DisplayFor:

LastName
@Html.DisplayFor(model => model.LastName)

Для отображения списка enrollments добавьте следующий код после поля EnrollmentDate, до закрывающего тега fieldset:

Нажмите на вкладку Students и щелкните на ссылку Details .

Создание страницы Create

В , замените код метода HttpPost Create:

Public ActionResult Create(Student student) { try { if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException) { //Log the error (add a variable name after DataException) ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator."); } return View(student); }
Таким образом мы добавляем сущность Student, созданную ASP.NET MVC Model Binder в соответствующее множество сущностей и затем сохраняем изменения в базу. (Model binder – функциональность ASP.NET MVC, облегчающая работа с данными, пришедшими из формы. Model binder конвертирует данные из формы в соответствующие типы данных.NET Framework и передает в нужный метод как параметры. В данном случае, model binder инстанциирует сущность Student используя значения свойства из коллекции Form.)

Блок try-catch является единственным различием нашего варианта и того, что было автоматически создано. Если исключение, наследованное от DataException, перехватывается во время сохранения изменений, выводится стандартное сообщение об ошибке. Такие ошибки обычно вызываются чем-то внешним нежели программистской ошибкой, поэтому пользователю просто предлагается попробовать ещё раз. Код в Views \ Student \ Create . cshtml похож на код из Details . cshtml за исключением EditorFor и ValidationMessageFor, используемых для каждого поля вместо хелпера DisplayFor. Следующий код приведен для примера:

@Html.LabelFor(model => model.LastName)
@Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName)

В Create . cshtml вносить изменения нет необходимости.

Нажмите на вкладку Students и на Create New .

Проверка данных включена по умолчанию. Введите имена и какую-нибудь неправильную дату и нажмите Create чтобы увидеть ошибку.

В этом случае вы видите проверку данных на клиентской стороне, реализованную с помощью JavaScript. Проверка данных на стороне сервера также реализована и, даже если проверка данных на стороне клиента пропустит плохие данные, они будут перехвачены на стороне сервера и будет выброшено исключение.

Измените дату на правильную, например, на 9/1/2005 и нажмите Create чтобы увидеть нового студента на странице Index .

Создание страницы Edit

В Controllers\StudentController.cs метод HttpGet Edit (тот, который без атрибута HttpPost) использует метод Find для извлечения выбранной сущности Student. Необходимости изменять код этого метода нет.

Замените код метода HttpPost Edit на следующий код:

Public ActionResult Edit(Student student) { try { if (ModelState.IsValid) { db.Entry(student).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException) { //Log the error (add a variable name after DataException) ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator."); } return View(student); }
Код похож на то, что было в методе HttpPost Create, однако вместо добавления сущности в множество этот код устанавливает свойство сущности, определяющее, было ли оно изменено. При вызове SaveChanges свойство Modified указывает Entity Framework на необходимость создания SQL запроса для обновления записи в базе. Все столбцы записи будут обновлены, включая те, которые пользователь не трогал. Вопросы параллелизма игнорируются. (о вопросах параллелизма можно почитать в Handling Concurrency .)

Состояния сущностей и методы Attach и SaveChanges Methods

Контекст базы данных следит за синхронизацией сущностей в памяти с соответствующими записями в базе, и эта информация определяет то, что происходит при вызове метода SaveChanges. Например, при передаче новой сущности в метод Add, состояние этой сущности меняется на Added. Затем, при вызове метода SaveChanges, контекст базы данных инициирует выполнение SQL-запроса INSERT.

Состояние сущности может быть определено как:

  • Added. Сущности еще нет в базе. Метод SaveChanges инициирует выполнение запроса INSERT.
  • Unchanged. При вызове SaveChanges ничего не происходит. Данное состояние у сущности при извлечении её из базы данных.
  • Modified. Значения свойств сущности были изменены, SaveChanges выполняет запрос UPDATE.
  • Deleted. Сущность помечена к удалению, SaveChanges выполняет запрос DELETE.
  • Detached. Состояние сущности не отслеживается контекстом базы данных.
В приложении для десктопа состояние меняется автоматически. В данном типе приложений вы извлекаете сущность и изменяете значения каких-либо свойств, что приводит к изменению состояния на Modified. После вызова SaveChanges Entity Framework генерирует SQL-запрос UPDATE, обновляющий только те свойства, значения которых были изменены.

Однако в веб-приложении алгоритм нарушается, так как экземпляр контекста базы данных, извлекающий сущность, уничтожается после перезагрузки страницы. При HttpPost Edit происходит новый запрос и у вас появляется новый экземпляр контекста, поэтому необходимо вручную менять состояние сущности на Modified. После этого, при вызове SaveChanges Entity Framework обновит все столбцы записи в базе, так как контекст уже не знает о том, какие свойства конкретно были изменены.

Если вы хотите, чтобы Update изменял только отредактированные пользователем поля, вы можете каким-либо образом сохранить оригинальные значения (например, скрытыми полями формы), сделав их доступными в момент вызова HttpPost Edit. Таким образом вы сможете создать сущность Student используя оригинальные значения, вызвать метод Attach с оригинальной версией сущности, обновить значения сущности и вызвать SaveChanges. За более подробной информацией можно обратиться к материалам Add/Attach and Entity States и посту в блоге команды разработки Entity Framework Local Data .

Код в Views \ Student \ Edit . cshtml аналогичен коду в Create . cshtml , вносить изменения не надо.

Нажмите на вкладке Students и затем на ссылку Edit .

Измените значения и нажмите Save .

Создание страницы Delete

В Controllers\StudentController.cs метод HttpGet Delete использует метод Find для извлечения выбранной сущности Student, также, как и в Details и Edit ранее. Для реализации своего сообщения об ошибке при ошибке вызова SaveChanges, необходимо добавить дополнительную функциональность к методу и соответствующему представлению.

Как и в случае с операциями обновления и создания, операция удаления также нуждается в двух методах. Метод, вызываемый в ответ на GET-запрос, показывает пользователю представлению, позволяющее подтвердить или отменить удаление. Если пользователь подтверждает удаление, создаётся POST-запрос и вызывается метод HttpPost Delete.

Вам необходимо добавить блок исключений try-catch в код метода HttpPost Delete для обработки ошибок, которые могут возникнуть при обновлении базы данных. Если возникает ошибка, метод HttpPost Delete вызывает метод HttpGet Delete, передавая ему параметр, сигнализирующий об ошибке. Метод HttpGet Delete снова генерирует страницу подтверждения удаления и текстом ошибки.

Замените код метода HttpGet Delete на следующий код, позволяющий обработать ошибки:

Public ActionResult Delete(int id, bool? saveChangesError) { if (saveChangesError.GetValueOrDefault()) { ViewBag.ErrorMessage = "Unable to save changes. Try again, and if the problem persists see your system administrator."; } return View(db.Students.Find(id)); }
Этот код принимает опциональный параметр булевого типа, сигнализирующий о возникновении ошибки. Этот параметр равен null (false) после вызова HttpGet Delete и true при вызове HttpPost Delete.

Замените код метода HttpPost Delete (DeleteConfirmed) следующим кодом, совершающим удаление и обрабатывающим ошибки:

Public ActionResult DeleteConfirmed(int id) { try { Student student = db.Students.Find(id); db.Students.Remove(student); db.SaveChanges(); } catch (DataException) { //Log the error (add a variable name after DataException) return RedirectToAction("Delete", new System.Web.Routing.RouteValueDictionary { { "id", id }, { "saveChangesError", true } }); } return RedirectToAction("Index"); }
Код возвращает выбранную сущность и вызывает метод Remove, меняющий состояние сущности на Deleted. При вызове SaveChanges генерируется SQL-запрос DELETE.

Если производительность – приоритет, то можно обойтись без ненужных SQL-запросов, возвращающих запись путём замены кода, вызывающего методы Find и Remove:

Student studentToDelete = new Student() { StudentID = id }; db.Entry(studentToDelete).State = EntityState.Deleted;
Этим кодом мы инстанцируем сущность Student используя только значение первичного ключа и затем определяем состояние сущности как Deleted. Это всё, что нужно Entity Framework для удаление сущности.

Как уже упоминалось, метод HttpGet Delete не удаляет данные. Удаление данных в ответ на GET-запрос (или, также, редактирование, создание и любое другое действие с данными) создаёт брешь в безопасности. Для подробной информации смотрите ASP.NET MVC Tip #46 - Don"t use Delete Links because they create Security Holes в блоге Stephen Walther.

В Views \ Student \ Delete . cshtml добавьте следующий код между h2 и h3:

@ViewBag.ErrorMessage

Нажмите на вкладку Students и затем на ссылку Delete :

Нажмите Delete . Загрузится страница Index, уже без студента, которого мы удалили (вы увидите пример обработки кода в методе в уроке Handling Concurrency .)

Убеждаемся, что не осталось открытых подключений к базе данных

Чтобы быть уверенным, что все подключения к базе данных были правильно закрыты и ресурсы, занятые ими, освобождены, необходимо убедиться в том, что контекст уничтожается. Поэтому вы можете найти метод Dispose в конце класса контроллера StudentController в StudentController Добавить метки

Связи между сущностями в модели - это отражение реляционных связей между таблицами в БД. Реляционные связи в таблицах реализованы через первичные и внешние ключи, связи в модели Entity на основе навигационных свойств сущностей.

В базе нашего проекта 2 таблицы: Tenants и Apartments. Они связаны отношением «один ко многим». Это значит, что в одной квартире могут проживать несколько жильцов, но один жилец может быть жить только в одной квартире.

На уровне кода (в классах модели) навигационные свойства становятся массивами с ссылками на объекты.

Public class Apartment { public int ApartmentID { get; set; } public decimal ApartСost { get; set; } public int Apartnumber { get; set; } public int NumberofRooms { get; set; } public virtual ICollection Tenants { get; set; } // Массив ссылок на объекты типа Tenant, связанные с этим экземпляром типа Apartment. Навигационное свойство. }

Один класс, становится контейнером для другого.

Три типа загрузки

Существует три типа загрузки связанных данных:

  • отложенная (ленивая);
  • безотложная (прямая);
  • явная.

Они различаются механизмом и производительностью.

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

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

Private RegistrationContext db = new RegistrationContext(); // Запрос, извлекающий из таблицы Apartments строку, где в столбце ApartNumber установлено значение 6. Apartment apartment = db.Apartments.Where(n=>n.ApartNumber == 6).FirstOrDefault(); // Второй запрос, извлекающий все объекты Tenant связанные данным объектом Apartment foreach (Tenant t in apartment.Tenants) { // Действия с данными }

Когда идет полная выборка, нам нужны два цикла (один внутри другого):

Foreach (Apartment a in db.Apartments) { foreach(Tenant t in a.Tenants) { // Как-то используем данные. } }

Класс DbContext использует ленивую загрузку по умолчанию. Однако, ленивая загрузка по умолчанию не сработает если:

  • класс модели запечатан (sealed);
  • объявлен с идентификатором доступа отличным от public;
  • навигационное свойство не объявлено виртуальным.

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

Прямая загрузка

Apartments = db.Apartments.Include(t=>t.Tenants) foreach (Apartment a in apartments) { foreach(Tenant t in a.Tenants) { // Как-то используем данные. } }

Хотя в примере выше использованы два цикла, запрос только один.

Допустимо использовать в одной цепочке методов сколько угодно вызовов Include.

Явная загрузка

Похожа на ленивую загрузку. Работает также, но требует явного указания инструкции загрузки (метод Load). Используется если, ленивая загрузка отключена.

В отличие от ленивой, явная загрузка использует для доступа к объекту сущности не свойства контекста (DbSet), а его метод Entry().

Var apartments = db.Apartments.ToList(); foreach (Apartment a in apartments) { // Указываем откуда и что нам нужно загрузить. Конец цепочки - вызов метода Load(). db.Entry(a).Collection(t => t.Tenants).Load(); foreach(Tenant t in a.Tenants) { // Что-то делаем с данными. } }

Обратите внимание на «явность» такого вызова.

  • .db // Объект контекста.
  • .Entry(a) // В параметре объект модели.
  • .Collection(t => t.Tenants) // Метод Collection, потому что навигационное свойство содержит массив ссылок. В параметре делегат, с указанием навигационного свойства.
  • .Load(); //Явный вызов загрузки.

Какую загрузку использовать

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

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

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

Создание страницы, отображающей информацию о квартирах и проживающих.

Мы собираемся сгенерировать контроллер и GRUD представления с помощью Visual Studio. Для этого в обозревателе решений вызовите контекстное меню на папке Controllers и выберите пункт «Добавить контроллер».

Выберите пункт Контроллер MVC 5 c представлениями, использующий Entity и нажмите «Добавить».

  • класс модели (Apartment);
  • имя контроллера (ApartmentController);
  • и класс контекста (RegistrationContext)

После нажатия «Добавить» Visual Studio сгенерирует вам контроллер с методами действия соответствующим CRUD - инструкциям. Нам предстоит их только поправить.

Наша цель - это страница. Которая может отображать одновременно две таблицы: первая с данными из таблицы Apartmets, вторая - данные из таблицы Tenants, связанные с выделенным элементом из первой таблицы.

Для этого нам понадобится совокупная модель представления включающая данные как из первой, так и из второй таблицы. Cоздадим ее в папке Views/ViewsModels в файле ApartmentIndexer.cs :

Using System.Collections.Generic; using EnrollmentApp.Models; namespace EnrollmentApp.Views.ViewsModel { public class ApartmentIndexer { public IEnumerable Apartments { get; set; } // Свойство для хранения коллекции объектов типа Apartment public IEnumerable Tenants { get; set; } // Свойство для хранения коллекции объектов типа Apartment } }

Чтобы мы могли работать с предыдущим классом в ApartmentController , добавьте в него ссылку на файл:

Using EnrollmentApp.Views.ViewsModel;

Теперь отредактируем метод Index, который будет отображать информацию из двух таблиц.

Public class ApartmentController: Controller { private RegistrationContext db = new RegistrationContext(); // GET: Apartment public ActionResult Index(int? id) { // создаем наш совокупный класс. var IndexModel = new ApartmentIndexer(); // Наполняем его элементами таблицы Apartments. IndexModel.Apartments = db.Apartments .Include(t => t.Tenants) .OrderBy(t => t.Apartnumber); if (id != null) { ViewBag.ApartmentID = id.Value; // Наполняем связанную коллекцию для каждой квартиры соотвествующими жильцами.Чтобы гарантировать, что жилец будет один применяем метод Single // Альтернатива – применить SingeOrDefault IndexModel.Tenants = IndexModel.Apartments.Where(i => i.ApartmentID == id.Value).Single().Tenants; } // Передаем класс-контейнер в представление. return View(IndexModel); } }

Этот метод принимает параметр id, которые будет генерировать ссылка в представлении. Благодаря параметру id, мы можем понять, какую квартиру пользователь выбрал и вывести на экран данные связанные с ней.

Осталось исправить представление Index, добавив в него две таблицы ссылки и отобразить данные:

@model EnrollmentApp.Views.ViewsModel.ApartmentIndexer @{ ViewBag.Title = "Квартиры"; }

Index

@Html.ActionLink("Добавить квартиру", "Create")

@foreach (var item in Model.Apartments) { }
Номер квартиры Количество комнат Цена
@Html.DisplayFor(modelItem => item.Apartnumber) @Html.DisplayFor(modelItem => item.NumberofRooms) @Html.DisplayFor(modelItem => item.ApartСost) @Html.ActionLink("Выбрать","Index",new { id = item.ApartmentID}) | @Html.ActionLink("Редактировать", "Edit", new { id=item.ApartmentID }) | @Html.ActionLink("Подробности", "Details", new { id=item.ApartmentID }) | @Html.ActionLink("Удалить", "Delete", new { id=item.ApartmentID })
@if (Model.Tenants != null) {

Жители этой квартиры

@foreach (var item in Model.Tenants) { }
Фамилия Имя и Отчество Дата регистрации
@item.LastName @item.FirstAndMidName @item.RegistrationDate
}

Теперь запустив приложение, перейдите на страницу Index. Страница отображает список квартир: