[ Полезный рекламный блок ]
Попробуйте свои силы в игре, где ваши навыки программирования на C# станут решающим фактором. Переходите по ссылке 🔰.
У цій статті, ми продовжимо писати магазин на Asp.Net Core і Entity Framework Core. У першій частині ми додали підтримку для збереження даних у БД і видачі запитів до неї. Для ознайомлення з першою частиною, перейдіть за посиланям.
Магазин на Asp.Net Core MVC EF. Частина 2
Модифікація та видалення даних
Оновлення контролера і створення подання
Оновлення тільки змінених властивостей
Використання засобу виявлення змін у файлі
Модифікація та видалення даних
Наразі застосунок GameStore здатний зберігати об’єкти Product у БД і виконувати запити для їхнього читання назад у пам’ять. Більшості додатків також потрібна можливість вносити зміни в дані після того, як вони були збережені, включаючи повне видалення об’єктів. У цьому розділі буде додано підтримку для оновлення та видалення об’єктів Product. До того ж обговорюються проблеми, з якими цілком імовірно ви зіткнетеся під час додавання таких засобів у власні проекти, і пояснюється, як їх вирішити.
Інфраструктура Entity Framework Core підтримує кілька способів оновлення об’єктів. У поточному розділі ми почнемо з найпростішого прийому, за якого об’єкт, створений зв’язувачем моделей MVC, застосовується для повної заміни об’єкта, що зберігається в БД.
Насамперед змініть інтерфейс IProduct, додавши до нього методи, які можна використовувати в решті коду застосунку для витягання й оновлення наявного об’єкта:
1 2 3 4 5 6 |
public interface IProduct { //... void AddProduct(Product product); void UpdateProduct(Product product); } |
Метод GetProduct() надасть одиночний об’єкт Product за значенням його первинного ключа. Метод UpdateProduct() отримує об’єкт Product і будь-якого результату не повертає.
Реалізуємо ці методи в класі репозиторії ProductRepository:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class ProductRepository : IProduct { //... public Product GetProduct(int id) { return _context.Products.Find(id); } public void UpdateProduct(Product product) { _context.Products.Update(product); _context.SaveChanges(); } } |
Об’єкт DbSet<Product>, що повертається властивістю Products контексту БД, надає функціональний засіб, який необхідний для реалізації нових методів. Метод Find() приймає значення первинного ключа і запитує у БД відповідний йому об’єкт. Метод Update() приймає об’єкт Product і застосовує його для поновлення БД, замінюючи об’єкт у БД із тим самим первинним ключем. Як і у випадку всіх операцій, які змінюють БД, після виклику Update() знадобиться викликати метод SaveChanges().
Пам’ятати про необхідність виклику методу SaveChanges() може здатися складним завданням, але це швидко увійде у звичку. Такий підхід означає, що ви можете підготувати безліч змін, викликаючи методи об’єкта контексту, і потім одночасно відправити їх БД за допомогою єдиного виклику SaveChanges().
Оновлення контролера і створення подання
Наступний крок полягає в оновленні контролера Home, щоб додати методи дій, які дадуть змогу користувачеві вибрати об’єкт Product для редагування та надіслати зміни додатку.
Додамо 2 нові дії в HomeController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class HomeController : Controller { //... [HttpGet] public IActionResult UpdateProduct(int id) { return View(_products.GetProduct(id)); } [HttpPost] public IActionResult UpdateProduct(Product product) { _products.UpdateProduct(product); return RedirectToAction(nameof(Index)); } } |
Ви можете помітити, як методи дій зіставляються із засобами, що надаються сховищем, за допомогою класу контексту БД. Щоб забезпечити контролер поданням для нових дій, додайте в папку Views/Homeфайл на ім’я UpdateProduct.cshtml, з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@{ ViewData["Title"] = "Обновление товара"; } @model Product <hЗ class="p-2 bg-primary text-white text-center">Обновление товара - @Model.Name</hЗ> <form asp-action="UpdateProduct" method="post"> <div class="form-group"> <label asp-for="Id"></label> <input asp-for="Id" class="form-control" readonly="readonly" /> </div> <div class="form-group"> <label asp-for="Name"></label> <input asp-for="Name" class="form-control" /> </div> <div class="form-group"> <label asp-for="Category"></label> <input asp-for="Category" class="form-control" /> </div> <div class="form-group"> <label asp-for="PurchasePrice"></label> <input asp-for="PurchasePrice" class="form-control" /> </div> <div class="form-group"> <label asp-for="RetailPrice"></label> <input asp-for="RetailPrice" class="form-control" /> </div> <div class="text-center"> <button class="btn btn-primary" type="submit">Сохранить</button> <a asp-action="Index" class="btn btn-secondary">Отмена</a> </div> </form> |
Подання UpdateProduct.cshtml постачає користувачеві НТМL-форму, яку можна використовувати для зміни властивостей об’єкта Product, за винятком властивості Id, що застосовується як первинний ключ. Первинні ключі нелегко змінити після того, як їх було призначено, і якщо потрібне інше значення ключа, то простіше видалити об’єкт і створити новий. З цієї причини до елемента input додано атрибут readonly, який дає змогу бачити значення властивості Id, але не змінювати його. Або поле з Id, можна взагалі приховати з очей користувача.
Для інтеграції засобу оновлення з рештою коду додатка додамо елемент button до кожного об’єкта Product, що відображається поданням Index.cshtml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@if (Model.Count() == 0) { <div class="row"> <div class="col text-center p-2">Нет данных</div> </div> } else { @foreach (Product product in Model) { <div class="row р-2"> <div class="col">@product.Name</div> <div class="col">@product.Category</div> <div class="col text-right">@product.PurchasePrice</div> <div class="col text-right">@product.RetailPrice</div> <div class="col"> <a asp-action="UpdateProduct" asp-route-id="@product.Id" class=" btn btn-outline-primary"> Изменить </a> </div> </div> } } |
Запустіть застосунок, виберіть будь-який товар і натисніть кнопку “Змінити”:
Змініть будь-яке значення на обраному товарі та натисніть кнопку “Зберегти”:
Якщо ви переглянете журнальні повідомлення, згенеровані додатком, то зможете побачити, як виконувані дії перетворюються на SQL-команди, що надсилаються серверу баз даних. Коли ви клацаєте на кнопці Edit, інфраструктура EntityFramework Core запитує у БД деталі обраного об’єкта за допомогою такої команди:
1 2 3 |
SELECT TOP(1) [p].[Id], [p].[Category], [p].[Name], [p].[PurchasePrice], [p].[RetailPrice] FROM[Products] AS[p] WHERE[p].[Id] = @__p_0 |
Метод Find(), транслюється в команду SELECT для одиночного об’єкта, який вказано з використанням ключового слова ТОР. Після клацання на кнопці Save інфраструктура Entity Framework Сare оновлює БД за допомогою такої команди:
1 2 3 |
UPDATE[Products] SET[Category] = @p0, [Name] = @p1, [PurchasePrice] = @p2, [RetailPrice] = @p3 WHERE[Id] = @p4; SELECT @@ROWCOUNT; |
Метод Update() транслюється в SQL-команду UPDATE, яка зберігає значення даних форми, отримані з НТТР-запиту.
Оновлення тільки змінених властивостей
Основні будівельні блоки для виконання оновлень на місці, але результат неефективний, оскільки інфраструктура Entity Framework Core не має в своєму розпорядженні вихідних даних для оцінки, що конкретно змінилося, і не має іншого вибору, крім збереження всіх властивостей. Щоб поглянути на проблему, клацніть на кнопці Edit для одного з товарів і потім клацніть на кнопці Save, не вносячи жодних змін. Хоча нові значення даних не вводилися, журнальні повідомлення, згенеровані додатком, свідчать про те, що випущена інфраструктурою EntityFramework Core команда UPDATE надсилає значення для всіх властивостей, визначених у класі Product:
1 2 |
UPDATE[Products] SET[Category] = @p0, [Name] = @p1, [PurchasePrice] = @p2, [RetailPrice] = @p3 WHERE[Id] = @p4; |
Інфраструктура Entity Framework Core містить засіб виявлення змін, що здатен виявити, які властивості було змінено. Для такого простого класу моделі даних, яким є Product, користь від цього засобу невелика, але в разі складніших моделей виявлення змін може бути важливим.
Засіб виявлення змін потребує вихідних даних, з якими можна було б порівняти дані, отримані від користувача. Існують різні способи надання вихідних даних (їх описано в розділі 12), але тут буде застосовано найпростіший підхід, який передбачає запитування у БД наявних даних.
Змінимо метод UpdateProduct, класу ProductRepository, таким чином:
1 2 3 4 5 6 7 8 9 10 |
public void UpdateProduct(Product product) { Product product2 = GetProduct(product.Id); product2.Name = product.Name; product2.Category = product.Category; product2.RetailPrice = product.RetailPrice; product2.PurchasePrice = product.PurchasePrice; //_context.Products.Update(product); _context.SaveChanges(); } |
Код об’єднує в додатку дві різні функції. Інфраструктура Entity Framework Core відстежує зміни в об’єктах, які створює з даних запитів, тоді як зв’язувач моделей MVC створює об’єкти з НТГР-даних. Згадані два джерела об’єктів не об’єднані, і якщо не дотримуватися обережності при утриманні їх роздільними, то виникнуть проблеми. Найбезпечніший спосіб задіяти у своїх інтересах відстеження змін – запросити БД і потім скопіювати значення з НТГР-даних, як було зроблено.
Коли викликається метод SaveChanges(), інфраструктура Entity Framework Core з’ясує, значення яких властивостей змінилися, і оновить у БД тільки ці властивості.
Запустіть додаток, виконайте зміну закупівельної вартості будь-якого продукту та перегляньте поточний запит:
1 2 |
UPDATE[Products] SET[PurchasePrice] = @p0 WHERE[Id] = @p1; |
Виконання масових оновлень
Масові оновлення часто потрібні в додатках, де передбачені окремі ролі адміністраторів, яким необхідно вносити зміни до безлічі об’єктів в єдиній операції. Точна природа оновлень буде варіюватися, але поширені причини для масових оновлень включають виправлення помилок у записах даних або перепризначення об’єктів у нові категорії, що може виявитися витратним за часом процесом у разі виконання на індивідуальних об’єктах. З використанням інфраструктури Entity Framework Core робити масові оновлення легко, але потрібні невеликі зусилля, щоб забезпечити їхню гладку роботу з частиною ASP.NEТ Core MVC додатка.
Зміна сховища
Додайте в інтерфейс сховища IProduct новий метод, який буде виконувати масове оновлення:
1 2 3 4 5 |
public interface IProduct { //... void UpdateAll(Product[] products); } |
Нарешті, додайте в сховище ProductRepository реалізацію методу UpdateAll(), який буде оновлювати БД із застосуванням даних, отриманих з НТТР-запиту:
1 2 3 4 5 6 7 8 9 10 |
public class ProductRepository : IProduct { //... public void UpdateAll(Product[] products) { _context.Products.UpdateRange(products); _context.SaveChanges(); } } |
Клас DbSet<T> пропонує методи для роботи з індивідуальними об’єктами і з колекціями об’єктів. У цьому прикладі використовується метод UpdateRange(), який є аналогом методу Update(), але працює з колекціями. Коли викликається метод SaveChanges(), інфраструктура Entity Framework Core відправляє послідовність SQL-команд UPDATE для оновлення БД.
Зміна подань і контролера
Для додавання підтримки масових оновлень змініть, подання Index, включивши в нього кнопку Edit All (Редагувати все), яка націлена на дію UpdateAll. Також додайте властивість типу ViewBag на ім’я UpdateAll. Якщо її значенням є true, тоді відображатиметься часткове подання InlineEditor.cshtml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
@{ ViewData["Title"] = "Все товары"; } @model IQueryable<Product> <h3 class="p-2 bg-bg-primary text-white text-center">Товары</h3> <div class="container"> @if (ViewBag.UpdateAll != true) { <div class="row"> <div class="col font-weight-bold">Название</div> <div class="col font-weight-bold">Категория</div> <div class="col font-weight-bold">Закупочная цена</div> <div class="col font-weight-bold">Розничная цена</div> <div class="col"></div> </div> <form asp-action="AddProduct" asp-controller="Home" method="post"> <div class="row"> <div class="col"> <input name="Name" class="form-control" required /> </div> <div class="col"> <input name="Category" class="form-control" required /> </div> <div class="col"> <input name="PurchasePrice" class="form-control" required /> </div> <div class="col"> <input name="RetailPrice" class="form-control" required /> </div> <div class="col"> <button type="submit" class="btn btn-primary">Добавить</button> </div> </div> </form> @if (Model.Count() == 0) { <div class="row"> <div class="col text-center p-2">Нет данных</div> </div> } else { @foreach (Product product in Model) { <div class="row р-2"> <div class="col">@product.Name</div> <div class="col">@product.Category</div> <div class="col text-right">@product.PurchasePrice</div> <div class="col text-right">@product.RetailPrice</div> <div class="col"> <a asp-action="UpdateProduct" asp-route-id="@product.Id" class="btn btn-outline-primary">Изменить</a> </div> </div> } } <div class="text-center"> <a asp-action="UpdateAll" class="btn btn-primary">Изменить все</a> </div> } else { @Html.PartialAsync("InlineEditor", Model) } </div> |
Створіть часткове подання, додавши в папку Views/Home файл на ім’я InlineEditor.cshtml, з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
@model IEnumerable<Product> <div class="row"> <div class="col-1 font-weight-bold">Id</div> <div class="col font-weight-bold">Название</div> <div class="col font-weight-bold">Категория</div> <div class="col font-weight-bold">Закупочная цена</div> <div class="col font-weight-bold">Розничная цена</div> </div> @{ int i = 0; } <form asp-action="UpdateAll" method="post"> @foreach (Product p in Model) { <div class="row"> <div class="col-1"> @p.Id <input type="hidden" name="Products[@i].Id" value="@p.Id"> </div> <div class="col"> @p.Name <input class="form-control" name="Products[@i].Name" value="@p.Name"> </div> <div class="col"> @p.Category <input class="form-control" name="Products[@i].Category" value="@p.Category"> </div> <div class="col"> @p.PurchasePrice <input class="form-control" name="Products[@i].PurchasePrice" value="@p.PurchasePrice"> </div> <div class="col"> @p.RetailPrice <input class="form-control" name="Products[@i].RetailPrice" value="@p.RetailPrice"> </div> </div> i++; } <div class="form-group text-center"> <button class="btn btn-primary" type="submit">Сохранить</button> <a asp-action="Index" class="btn btn-secondary">Отмена</a> </div> </form> |
Часткове подання створює набір елементів форми, імена яких слідують угоді, прийнятій у МVС для колекції об’єктів, а тому властивості Id призначаються імена Products[0].Id, Products[1].Id і т.д. Встановлення імен елементів input вимагає лічильника, що породжує незграбну суміш виразів Razor і С#.
Додайте в контролер Ноmе методи дій, які дадуть змогу користувачеві почати процес масового редагування і відправити дані:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class HomeController : Controller { //... [HttpGet] public IActionResult UpdateAll() { ViewBag.UpdateAll = true; return View(nameof(Index), _products.GetAllProducts()); } [HttpPost] public IActionResult UpdateAll(Product[] products) { _products.UpdateAll(products); return RedirectToAction(nameof(Index)); } } |
На цьому етапі структура проєкту має такий вигляд:
Запустіть додаток і виконайте масове редагування товарів:
Використання засобу виявлення змін у файлі
У попередньому коді не застосовується засіб виявлення змін Entity Framework Core, тобто будуть оновлюватися всі властивості всіх об’єктів Product. Щоб оновлювати тільки змінені значення, змініть метод UpdateAll, у класі ProductRepository, таким чином:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class ProductRepository : IProduct { //... public void UpdateAll(Product[] products) { // _context.Products.UpdateRange(products); Dictionary<int, Product> data = products.ToDictionary(e => e.Id); IEnumerable<Product> baseline = _context.Products.Where(e => data.Keys.Contains(e.Id)); foreach (Product product in baseline) { Product requestProduct = data[product.Id]; product.Name = requestProduct.Name; product.Category = requestProduct.Category; product.RetailPrice = requestProduct.RetailPrice; product.PurchasePrice = requestProduct.PurchasePrice; } _context.SaveChanges(); } } |
Процес виконання оновлення може виглядати заплутаним. Спочатку створюється словник об’єктів Product, отриманих від зв’язувача моделей MVC, із застосуванням властивості Id для ключів. Колекція ключів використовується для запитування відповідних об’єктів у БД. Далі відбувається перерахування об’єктів БД із копіюванням значень властивостей з об’єктів НТТР-запиту. Після виклику методу SaveChanges() інфраструктура EF виконує виявлення змін і оновлює тільки ті властивості, значення яких змінилися.
Запустіть додаток і виконайте масове редагування товарів. Перегляньте журнальні повідомлення, згенеровані додатком. Порівняйте запити з різними версіями методу UpdateAll.
Видалення даних
Видалення об’єктів із БД – процес простий, хоча з розростанням моделі даних він може ускладнитися. В інтерфейс IProduct додамо такий метод:
1 2 3 4 5 |
public interface IProduct { //... void DeleteProduct(Product product); } |
У класі ProductRepository реалізуємо цей метод:
1 2 3 4 5 6 7 8 9 10 |
public class ProductRepository : IProduct { //... public void DeleteProduct(Product product) { _context.Products.Remove(product); _context.SaveChanges(); } } |
Клас DbSet<T> має методи Remove() і RemoveRange(), призначені для видалення одного і декількох об’єктів з БД. Як і в разі інших операцій, що модифікують БД, дані не видаляються доти, доки не буде викликано метод SaveChanges().
Додайте в контролер Home метод дії, який отримує з НТТР запиту деталі об’єкта Product, що підлягає видаленню, і передає їх сховищу:
1 2 3 4 5 6 7 8 9 10 |
public class HomeController : Controller { //... [HttpPost] public IActionResult DeleteProduct(Product product) { _products.DeleteProduct(product); return RedirectToAction(nameof(Index)); } } |
У подання Index додамо елемент form:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@foreach (Product product in Model) { <div class="row р-2"> <div class="col">@product.Name</div> <div class="col">@product.Category</div> <div class="col text-right">@product.PurchasePrice</div> <div class="col text-right">@product.RetailPrice</div> <div class="col"> <form asp-action="DeleteProduct" method="post"> <input type="hidden" name="Id" value="@product.Id"> <button type="submit" class="btn btn-outline-danger">Удалить</button> </form> <a asp-action="UpdateProduct" asp-route-id="@product.Id" class="btn btn-outline-primary">Изменить</a> </div> </div> } |
Зверніть увагу, що форма містить єдиний елемент input для властивості Id. Це все, що інфраструктура Entity Framework Core застосовує для видалення об’єкта з БД, хоча операція виконується над повним об’єктом Product. Замість надсилання додаткових даних, які не планується використовувати, було надіслано тільки значення первинного ключа, яке зв’язувач моделей MVC застосує для створення об’єкта Product, залишаючи всі інші властивості рівними null або стандартному значенню для типу.
Запустіть додаток і виконайте видалення будь-якого продукту.
Підсумок
У розділі до додатка GameStore було додано підтримку для оновлення та видалення об’єктів. Ви дізналися, яким чином модифікувати індивідуальні об’єкти і виконувати масові оновлення, а також як постачати інфраструктуру Entity Framework Core вихідними даними для її засобу виявлення змін. Крім того, було показано, як видаляти дані, що робиться просто у випадку моделі даних з єдиним класом і ускладнюється з розростанням моделі даних. У наступному уроці модель даних для додатка GameStore буде розширено.
На цьому стаття “Магазин на Asp.Net Core MVC EF 2”, підійшла до кінця, сподіваюся вам було цікаво. Ви можете завантажити вихідний код у моєму репозиторії — Github.
Поділіться вашим досвідом у коментарях, як ви створюєте магазин на Asp.Net Core MVC EF?
Так само вам може бути цікава попередня стаття:
Ви хочете навчитися писати код мовою програмування C#?
Створювати різні інформаційні системи, що складаються з сайтів, мобільних клієнтів, десктопних додатків, телеграм-ботів тощо.
Переходьте до нас на сторінку Dijix і ознайомтеся з умовами навчання, ми спеціалізуємося тільки на індивідуальних заняттях, як для початківців, так і для просунутих програмістів. Ви можете взяти як одне заняття для опрацювання питання, що вас цікавить, так і кілька, для більш щільної роботи. Завдяки особистому кабінету, кожен студент підвищить якість свого навчання, у вашому розпорядженні:
- Доступ до пройденого матеріалу
- Тематичні статті
- Бібліотека книг
- Онлайн тестування
- Спілкування в закритих групах