[ Полезный рекламный блок ]
Попробуйте свои силы в игре, где ваши навыки программирования на C# станут решающим фактором. Переходите по ссылке 🔰.
У цьому уроці розглянемо процес побудови реалістичного проєкту, який продемонструє спільне використання ASP.NET Core MVC і Entity Framework Core. Проєкт буде простим, але близьким до реальності, і фокусуватиметься на часто вживаних засобах Entity Framework Core.
Я очікую від вас мінімальні знання концепцій Entity Framework Core, тому в деяких місцях, вдаватися в подробиці не стану.
Магазин на Asp.Net Core MVC EF
Створення та налаштування проекту
Додавання контролера та подання
Зберігання даних з Entity Framework Core
Створення класу контексту даних
Конфігурування постачальника бази даних і класу контексту
Уникнення пасток, пов’язаних із запитами
У цьому уроці ми створимо простий самодостатній застосунок ASP.NET Core MVC, додамо підтримку інфраструктури Entity Framework Core і збереження даних застосунку в БД.
У наступному уроці, додамо додаткові операції над даними, розширену модель даних, забезпечення підтримки засобів для покупців і розглянемо, як масштабувати додаток.
Створення та налаштування проекту
Щоб створити проєкт GameStore, запустіть Visual Studio і виберіть у меню File (Файл) – New Project (Створити проєкт). Вкажіть шаблон проекту ASP.NET Core Web Application (Веб-додаток ASP.NET Core). Введіть GameStore, у полі Name, на наступній сторінці вкажіть Framework .Net 6.0 і натисніть кнопку Create:
Модель і Репозиторій
Модель для додатка GameStore буде заснована на списку товарів. Створіть папку Models і додайте в неї файл класу на ім’я Product.cs з таким вмістом:
1 2 3 4 5 6 7 |
public class Product { public string Name { get; set; } public string Category { get; set; } public decimal PurchasePrice { get; set; } public decimal RetailPrice { get; set; } } |
Мені подобається забезпечувати узгоджений доступ до даних у додатку з використанням патерну “Сховище” (Repository), у якому інтерфейс визначає властивості та методи, призначені для доступу до даних, а для роботи з механізмом зберігання даних застосовується клас реалізації. Перевага використання патерну “Сховище” пов’язана з полегшенням модульного тестування частини MVC додатка, а також із тим, що деталі, які стосуються зберігання даних, приховані від інших частин додатка.
Щоб створити інтерфейс сховища, створіть папку Interfaces і додайте в неї файл інтерфейсу на ім’я IProduct.cs з таким вмістом:
1 2 3 4 5 |
public interface IProduct { IEnumerable<Product> GetAllProducts(); void AddProduct(Product product); } |
Метод GetAllProducts() надаватиме доступ тільки для читання до всіх товарів, відомих застосунку. Метод AddProduct() буде використовуватися для додавання нових товарів.
На цьому етапі ми збираємося зберігати об’єкти моделі в пам’яті, а трохи пізніше замінимо це інфраструктурою Entity Framework Core. Створіть папку Repository і додайте в неї файл класу на ім’я ProductRepository.cs з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class ProductRepository : IProduct { private List<Product> products; public ProductRepository() { products = new List<Product>(); } public void AddProduct(Product product) { products.Add(product); } public IEnumerable<Product> GetAllProducts() { return products; } } |
Клас ProductRepository реалізує інтерфейс IProduct і застосовує екземпляр List для відстеження об’єктів Product, тобто в разі зупинки або перезапуску застосунку дані будуть втрачатися. Постійне сховище буде введено в пізніше, а сховища в пам’яті цілком достатньо для приведення в працездатний стан частини ASP.NET Core МVС проекту, перш ніж додавати частину EntityFramework Core.
Додайте в клас Program наступний оператор, щоб зареєструвати клас ProductRepository як реалізацію для використання в якості залежностей інтерфейсу IProduct:
1 |
builder.Services.AddSingleton<IProduct, ProductRepository>(); |
Цей рядок коду реєструє клас ProductRepository із застосуванням методу AddSingleton. У результаті під час розпізнавання залежності інтерфейсу IProduct уперше створюється одиночний об’єкт, який буде використовуватися для всіх наступних залежностей.
Будь-який магазин на Asp.Net Core MVC, не зможе нормально функціонувати без контролерів і уявлень. Перейдемо до їх створення.
Додавання контролера та подання
Основний акцент у розглянутому прикладі програми зроблено на управлінні об’єктами товарів, тому що це створює чудову можливість для демонстрації різних засобів роботи з даними. Нам потрібен контролер, який отримуватиме НТТР-запити і транслюватиме їх в операції над об’єктами Product, тож створіть папку Controllers, додайте до неї файл на ім’я HomeController.cs, з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class HomeController : Controller { private readonly IProduct _products; public HomeController(IProduct products) { _products = products; } [HttpGet] public IActionResult Index() { return View(_products.GetAllProducts()); } [HttpPost] public IActionResult AddProduct(Product product) { _products.AddProduct(product); return RedirectToAction(nameof(Index)); } } |
Метод дії Index() передає колекцію об’єктів Product зі сховища своєму поданню, яке відобразить користувачеві екранну таблицю з даними. Метод AddProduct() зберігає нові об’єкти Product, які засновані на даних, отриманих у НТТР-запиті POST. Далі створіть папку Views/Home і помістіть у неї файл на ім’я 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 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 |
@{ ViewData["Title"] = "Все товары"; } @model IEnumerable<Product> <h3 class="p-2 bg-bg-primary text-white text-center">Товары</h3> <div class="container"> <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"></div> </div> } } </div> |
Файл Index.cshtml є поданням, яке відображатиме дані Product додатка і дозволятиме користувачеві створювати нові об’єкти.
Сіткове компонування застосовується для відображення вбудованої форми, за допомогою якої створюються нові об’єкти, поряд із деталями всіх відомих додатку об’єктів Product або заповнювачем, якщо об’єкти відсутні.
Запустимо додаток і виконаємо додавання товару:
Зараз дані програми зберігаються в пам’яті, тобто в разі зупинки або перезапуску програми відомості про всі товари втрачаються. У наступному розділі в проєкт буде додано підтримку Entity Framework Сore для забезпечення постійного зберігання даних у БД.
Зберігання даних з Entity Framework Core
Конфігурація підключення
У цьому розділі демонструється збереження даних додатка GameStore у БД. Буде показано, як додати інфраструктуру Entity Framework Core до проєкту, як підготувати модель даних, як створити та використовувати БД і як змусити додаток робити ефективні SQL-запити.
Для початку завантажимо бібліотеку Entity Framework Core Sql Server:
Або через Package Manager Console:
1 |
Install-Package Microsoft.EntityFrameworkCore.SqlServer |
Перейдемо у файл appsettings.json і пропишемо конфігурацію підключення та логування:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\mssqllocaldb;Database=gamestore;Trusted_Connection=True;" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Information" } }, "AllowedHosts": "*" } |
Рядки підключення визначаються у файлі appsettings.json, у коді вище наведено визначення рядка підключення для БД додатка GameStore. У проєкті застосовується версія LocalDB продукту SQL Server, яка спроєктована спеціально для розробників і не вимагає конфігурації або облікових даних.
Ви зобов’язані забезпечити, щоб рядок підключення був єдиним нерозривним рядком. Формат рядка підключення специфічний для кожного сервера баз даних.
Навіть у проєкті, що зберігає лише невеликий обсяг даних, важливо розуміти запити і команди SQL, які інфраструктура Entity Framework Core посилає серверу баз даних.
Підготовка моделі даних
Для збереження даних у БД інфраструктура Еntity Framework Core повинна вміти унікальним чином ідентифікувати кожен об’єкт, що вимагає вибору властивості, яка буде використовуватися як первинний ключ. У більшості проектів найпростіший спосіб визначення первинного ключа передбачає додавання в клас моделі даних властивості типу int на ім’я Id.
Змінимо клас Product, таким чином:
1 2 3 4 5 6 7 8 9 |
public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal PurchasePrice { get; set; } public decimal RetailPrice { get; set; } } |
Цей підхід означає, що інфраструктура Entity Framework Core конфігуруватиме БД так, щоб сервер баз даних генерував значення первинного ключа, і вам не довелося турбуватися про уникнення дублікатів. Застосування значення int гарантує наявність великого діапазону значень первинного ключа, і більшість проєктів будуть у змозі необмежено зберігати дані, не турбуючись про те, що ключі закінчаться.
Створення класу контексту даних
У забезпеченні доступу до даних у БД інфраструктура Entity Framework Core покладається на клас контексту БД. Щоб забезпечити приклад додатка контекстом, додайте в папку Models файл класу на ім’я ApplicationContext.cs із таким кодом:
1 2 3 4 5 6 7 8 |
public class ApplicationContext:DbContext { public ApplicationContext(DbContextOptions<ApplicationContext> context):base(context) { } public DbSet<Product> Products { get; set; } } |
Коли інфраструктура Entity Framework Core використовується для збереження простої моделі даних на кшталт тієї, що визначена в додатку GameStore. Клас контексту БД відповідно простий – хоча ситуація зміниться зі зростанням складності моделі даних у наступних розділах.
Поточна структура проєкту має такий вигляд:
Зміна реалізації сховища
Тепер настав час оновити клас реалізації сховища, щоб звертатися до даних через клас контексту, визначений раніше. Змінимо код класу ProductRepository.cs, таким чином:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class ProductRepository : IProduct { private ApplicationContext _context; public ProductRepository(ApplicationContext context) { _context = context; } public void AddProduct(Product product) { _context.Products.Add(product); _context.SaveChanges(); } public IEnumerable<Product> GetAllProducts() { return _context.Products; } } |
У додатку ASP.NET Core MVC, доступ до об’єктів контексту даних керується з використанням впровадження залежностей, і тому до класу ProductRepository було додано конструктор, який приймає об’єкт ApplicationContext, який буде надано засобом впровадження залежностей під час виконання.
Властивість Products, визначену інтерфейсом сховища, можна реалізувати шляхом повернення властивості DbSet<Product>, яка визначена в класі контексту. Аналогічно метод AddProduct() реалізувати легко, тому що об’єкт DbSet<Product> визначає метод Add(), який приймає об’єкти Product і зберігає їх на постійній основі.
Конфігурування постачальника бази даних і класу контексту
Додайте в клас Program.cs оператори конфігурації, щоб повідомити інфраструктуру Entity Framework Core про те, яким чином використовувати рядок підключення, який має застосовувати постачальник БД, і як керувати класом контексту:
1 2 3 4 5 6 7 |
builder.Services.AddTransient<IProduct, ProductRepository>(); IConfigurationRoot _confString = new ConfigurationBuilder(). SetBasePath(AppDomain.CurrentDomain.BaseDirectory).AddJsonFile("appsettings.json").Build(); builder.Services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(_confString.GetConnectionString("DefaultConnection"))); |
Весь код файла Program.cs:
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 |
using GameStore.Interfaces; using GameStore.Models; using GameStore.Repository; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); //Теперь не используем //builder.Services.AddSingleton<IProduct, ProductRepository>(); builder.Services.AddTransient<IProduct, ProductRepository>(); IConfigurationRoot _confString = new ConfigurationBuilder(). SetBasePath(AppDomain.CurrentDomain.BaseDirectory).AddJsonFile("appsettings.json").Build(); builder.Services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(_confString.GetConnectionString("DefaultConnection"))); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); |
Метод AddDbContext<T>(), що розширює, застосовується для налаштування класу контексту, вказує інфраструктурі Entity Framework Core, який постачальник БД використовувати (у цьому разі за допомогою методу UseSqlServer(), але для кожного постачальника БД передбачено свій метод), і надає рядок підключення.
Зверніть увагу, що змінено також і метод, який конфігурує впровадження залежностей для інтерфейсу IRepository:
1 |
builder.Services.AddTransient<IProduct, ProductRepository>(); |
У розділі “Модель і Репозиторій” за допомогою методу AddSingleton() забезпечувалося застосування одиночного об’єкта ProductRepository для розпізнавання всіх залежностей інтерфейсу IProduct, що було важливо, оскільки дані додатка зберігалися в екземплярі List, і бажано було завжди використовувати той самий об’єкт. Тепер, коли застосовується інфраструктура Entity Framework Core, задіяно метод AddTransient(), який гарантує створення нового об’єкта ProductRepository при кожному розпізнаванні залежності інтерфейсу IProduct. Такий підхід важливий, тому що інфраструктура Entity Framework Core розраховує на створення нового об’єкта контексту для кожного НТТР-запиту в застосунку ASP.NET Core МVС.
Створення бази даних
У попередньому розділі для інфраструктури Entity Framework Core було вказано вид даних, що підлягають зберіганню, і спосіб підключення до сервера баз даних. Далі необхідно створити БД.
Інфраструктура Entity Framework Core управляє базами даних через засіб, званий міграціями, які являють собою набори змін, що створюють або модифікують БД з метою її синхронізації з моделлю даних.
Для роботи з міграцією, необхідно завантажити таку бібліотеку:
1 |
Microsoft.EntityFrameworkCore.Tools |
Або через Package Manager Console:
1 |
Install-Package Microsoft.EntityFrameworkCore.Tools |
Для створення міграції у вікні Package Manager Console введіть таку команду:
1 |
Add-Migration Init |
Init або Initial – загальноприйняте ім’я, яке використовується для міграції, що здійснює початкову підготовку БД.
Під час виконання команди з лістингу 5.8 інфраструктура Entity Framework Сare інспектує проєкт, знаходить клас контексту і застосовує його для створення міграції. У результаті у вікні Solution Explorer з’явиться папка Migrations, що містить файли класів, оператори яких підготують БД.
Просто створити міграцію, яка являє собою всього лише набір інструкцій, недостатньо. Інструкції міграції мають бути виконані, щоб створити БД, яка зможе зберігати дані програми. Для виконання інструкцій міграції, у вікні Package Manager Console виконайте команду:
1 |
Update-Database |
Інфраструктура Entity Fгamework Core підключиться до сервера баз даних, зазначеного в рядку підключення, і виконає оператори в міграції. Результатом буде БД, яку можна використовувати для зберігання об’єктів Product.
Виконання програми
Основна підтримка для постійного зберігання об’єктів Product на місці, і додаток готовий до тестування, незважаючи на те, що робота над ним ще не завершена. Запустіть застосунок, спробуйте додати дані, протестувати його роботу.
Інтерфейс користувача залишився незмінним, але внутрішньо інфраструктура Entity Framework Сore зберігає дані в БД. Зупиніть і перезапустіть додаток, ви побачите, що введені раніше дані, як і раніше, доступні.
Уникнення пасток, пов’язаних із запитами
Додаток функціонує, дані зберігаються в БД, але все ще є робота, яку потрібно зробити, щоб витягти максимум з Entity Framework Core. Зокрема, необхідно уникнути двох поширених пасток. Такі проблеми можна ідентифікувати, досліджуючи SQL-запити, які Entity Framework Core посилає БД. З цією метою додайте в метод дії Index() контролера Home оператор, що полегшує перегляд запитів до БД, які ініціюються НТТР-запитом:
1 2 3 4 5 6 |
[HttpGet] public IActionResult Index() { System.Console.Clear(); return View(_products.GetAllProducts()); } |
Метод Console.Clear() очищає консоль, коли викликається дія Index, тож запити до БД із попередніх НТТР-запитів не буде видно. Запустіть додаток і перегляньте журнальні повідомлення, які відобразилися.
Інфраструктура Entity Fгamework Core полегшує запитування БД із застосуванням LINQ, хоча й не завжди працює так, як можна було б очікувати. У поданні Index, використовуваному контролером Home, для з’ясування кількості об’єктів Product, які були збережені в БД, застосовується метод Count() з LINQ:
1 2 3 4 5 6 |
@if (Model.Count() == 0) { <div class="row"> <div class="col text-center p-2">Нет данных</div> </div> } |
Щоб визначити, скільки Product є в БД, інфраструктура Entity Framework використовує SQL оператор SELECT для отримання всіх доступних даних Product, застосовує отримані дані для створення послідовності та потім підраховує їх.
Коли в БД є тільки три об’єкти, складнощів не виникає, але зі зростанням кількості об’єктів обсяг роботи, необхідної для їх підрахунку в такій манері, стає проблемою. Ефективніший підхід передбачає виконання підрахунку сервером баз даних, що звільняє інфраструктуру Еntity Framework Core від необхідності переміщення всіх даних і створення об’єктів.
Реалізувати такий підхід можна, внісши в тип моделі подання одну просту зміну, змінити тип моделі з IEnumerable на IQueryable:
1 |
@model IQueryable<Product> |
Перезавантаживши вікно браузера, ви побачите, що перші два запити, які Еntity Framework Core посилає серверу баз даних, змінилися:
Запит SELECT COUNT пропонує серверу баз даних підрахувати кількість об’єктів Product. не витягуючи дані і не створюючи будь-які об’єкти в додатку.
Отримання різних запитів для різних типів моделей уявлень може виглядати як нелогічна поведінка, і розуміння причин, чому так відбувається, життєво важливе в гарантуванні інфраструктурі Еntity Framework Core можливостей ефективного запитування баз даних.
Інфраструктура Еntity Framework Core включає дублюючий набір розширювальних методів LINQ, які оперують на об’єктах, що реалізують інтерфейс IQueryable<T>. Цей інтерфейс представляє запит до БД, і таке дублювання означає, що операції на кшталт Count() можуть так само легко виконуватися над даними в БД, як над об’єктами в пам’яті.
Підсумок
В уроці було додано підтримку для збереження даних у БД і видачі запитів до неї. Наводилися пояснення процесу переходу до постійного сховища даних і демонстрації, яким чином запити, ініційовані додатком, повинні адаптуватися до ефективної роботи з інфраструктурою Entity Framework Core.
У наступному уроці буде додано засоби для модифікації та видалення даних у БД.
На цьому стаття “Магазин на Asp.Net Core MVC EF”, підійшла до кінця, сподіваюся вам було цікаво. Ви можете завантажити вихідний код у моєму репозиторії — Github.
Поділіться вашим досвідом у коментарях, який був ваш перший проєкт, магазин на Asp.Net Core MVC EF або щось інше?
Так само вам може бути цікава попередня стаття:
Ви хочете навчитися писати код мовою програмування C#?
Створювати різні інформаційні системи, що складаються з сайтів, мобільних клієнтів, десктопних додатків, телеграм-ботів тощо.
Переходьте до нас на сторінку Dijix і ознайомтеся з умовами навчання, ми спеціалізуємося тільки на індивідуальних заняттях, як для початківців, так і для просунутих програмістів. Ви можете взяти як одне заняття для опрацювання питання, що вас цікавить, так і кілька, для більш щільної роботи. Завдяки особистому кабінету, кожен студент підвищить якість свого навчання, у вашому розпорядженні:
- Доступ до пройденого матеріалу
- Тематичні статті
- Бібліотека книг
- Онлайн тестування
- Спілкування в закритих групах