[ Полезный рекламный блок ]
Попробуйте свои силы в игре, где ваши навыки программирования на 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, не сможет нормально функционировать без контролеров и представлений. Перейдем к их созданию.
Добавление контроллера и представления
Основной акцент в рассматриваемом примере приложения сделан на управлении объектами товаров, потому что это создает великолепную возможность для демонстрации различных средств работы с данными. Нам нужен контроллер, который будет получать НТTP-запросы и транслировать их в операции над объектами 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, которые основаны на данных, полученных в НТTP-запросе 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 Соrе для обеспечения постоянного хранения данных в БД.
Хранение данных с 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 посылает серверу баз данных.
Подготовка модели данных
Для сохранения данных в БД инфраструктура Eпtity 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 Fгamework 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 Саге инспектирует проект, находит класс контекста и применяет его для создания миграции. В результате в окне Solution Explorer появится папка Migrations, содержащая файлы классов, операторы которых подготовят БД.
Просто создать миграцию, которая представляет собой всего лишь набор инструкций, недостаточно. Инструкции миграции должны быть выполнены, чтобы создать БД, которая сможет хранить данные приложения. Для выполнения инструкций миграции, в окне Package Manager Console выполните команду:
1 |
Update-Database |
Инфраструктура Entity Fгamework Core подключится к серверу баз данных, указанному в строке подключения, и выполнит операторы в миграции. Результатом будет БД, которую можно использовать для хранения объектов Product.
Выполнение приложения
Основная поддержка для постоянного хранения объектов Product на месте и приложение готово к тестированию, несмотря на то, что работа над ним еще не завершена. Запустите приложение, попробуйте добавить данные, протестировать его работу.
Пользовательский интерфейс остался неизменным, но внутренне инфраструктура Entity Framework Соге сохраняет данные в БД. Остановите и перезапустите приложение, вы увидите, что введенные ранее данные по-прежнему доступны.
Избегание ловушек, связанных с запросами
Приложение функционирует, данные сохраняются в БД, но все еще есть работа, которую нужно сделать, чтобы извлечь максимум из 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, так что запросы к БД из предшествующих НTТР-запросов не будут видны. Запустите приложение и просмотрите журнальные сообщения, которые отобразились.
Инфраструктура 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, применять полученные данные для создания последовательности и затем подсчитывает их.
Когда в БД есть только три объекта, сложностей не возникает, но с ростом количества объектов объем работы, требуемой для их подсчета в такой манере, становится проблемой. Более эффективный подход предусматривает выполнение подсчета сервером баз данных, что освобождает инфраструктуру Eпtity Framework Core от необходимости перемещения всех данных и создания объектов.
Реализовать такой подход можно, внеся в тип модели представления одно простое изменение, изменить тип модели с IEnumerable на IQueryable:
1 |
@model IQueryable<Product> |
Перезагрузив окно браузера, вы увидите, что первые два запроса, которые Eпtity Framework Core посылает серверу баз данных, изменились:
Запрос SELECT COUNT предлагает серверу баз данных подсчитать количество объектов Product. не извлекая данные и не создавая любые объекты в приложении.
Получение разных запросов для разных типов моделей представлений может выглядеть как нелогичное поведение, и понимание причин, почему так происходит, жизненно важно в гарантировании инфраструктуре Eпtity Framework Core возможностей эффективного запрашивания баз данных.
Инфраструктура Eпtity Framework Core включает дублирующий набор расширяющих методов LINQ, которые оперируют на объектах, реализующих интерфейс IQueryable<T>. Этот интерфейс представляет запрос к БД, и такое дублирование означает, что операции вроде Count() могут столь же легко выполняться над данными в БД, как над объектами в памяти.
Итог
В уроке была добавлена поддержка для сохранения данных в БД и выдачи запросов к ней. Приводились объяснения процесса перехода к постоянному хранилищу данных и демонстрации, каким образом запросы, инициируемые приложением, должны адаптироваться к эффективной работе с инфраструктурой Entity Framework Core.
В следующем уроке будут добавлены средства для модификации и удаления данных в БД.
На этом статья «Магазин на Asp.Net Core MVC EF», подошла к концу, надеюсь вам было интересно. Вы можете скачать исходный код в моем репозитории — Github.
Поделитесь вашим опытом в комментариях, какой был ваш первый проект, магазин на Asp.Net Core MVC EF или что-то другое?
Так же вам может быть интересна предыдущая статья:
Вы хотите научится писать код на языке программирования C#?
Создавать различные информационные системы, состоящие из сайтов, мобильных клиентов, десктопных приложений, телеграмм-ботов и т.д.
Переходите к нам на страницу Dijix и ознакомьтесь с условиями обучения, мы специализируемся только на индивидуальных занятиях, как для начинающих, так и для более продвинутых программистов. Вы можете взять как одно занятие для проработки интересующего Вас вопроса, так и несколько, для более плотной работы. Благодаря личному кабинету, каждый студент повысит качество своего обучения, в вашем распоряжении:
- Доступ к пройденному материалу
- Тематические статьи
- Библиотека книг
- Онлайн тестирование
- Общение в закрытых группах
Живи в своем мире, программируй в нашем.
Очень четко написано!
Спасибо большое за ваш труд!
Вот это да, настолько подробно и красиво написано, спасибо вам большое!
У вас в дальнейшем планируются подобного рода гайды?
Лучшее, что я видел…
Один из хороших примеров за последнее время ( чаще всего все устарело, или на старых вверсиях, что для начинающих боль и слезы).