Содержание
До этого момента мы, в основном, занимались настройкой нашего приложения для работы с базой данных MySQL в Blazor Server: создали и настроили контекст данных, фабрику контекстов, наполнили базу данных начальными данными, а также научились использовать миграции для синхронизации нашей базы данных и модели. Теперь рассмотрим основные операции с данными: создание (Create), чтение (Read), обновление (Update) и удаление (Delete) данных, для которых также используется акроним CRUD.
Тестовый проект
Для работы будем использовать пример, рассмотренный в предыдущей части руководства. На данный момент, для синхронизации базы данных и модели мы используем миграции, а сама модель данных и контекст выглядят следующим образом:
public class User { public int Id { get; set; } public string? Name { get; set; } public string? Email { get; set; } public int Age { get; set; } public string? Role { get; set; } //новое свойство - роль пользователя в системе public DateTime? CreatedAt { get; set; } }
Контекст:
public class ApplicationContext : DbContext { public DbSet<User> Users { get; set; } public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options) { Database.Migrate(); //_ = Database.EnsureCreated(); } }
Полный исходник тестового проекта можно скачать из Github.
Добавление данных в базу (Create)
Для добавление новый данных в EF Core используются методы DbSet<TEntity>.Add
, DbSet<TEntity>.AddAsync
, DbSet<TEntity>.AddRange
и DbSet<TEntity>.AddRangeAsync
. Для создания новой записи пользователя в нашем приложении Blazor Server создадим новый компонент Blazor следующего содержания:
@page "/adduser" @using EFCoreBlazorMigrations.Models @using Microsoft.EntityFrameworkCore @using EFCoreBlazor; @inject NavigationManager _navigation @inject IDbContextFactory<ApplicationContext> DbFactory <h3>Новый пользователь</h3> <EditForm Model="user" OnSubmit="OnSubmit"> <label for="userName" class="form-label"> Введите имя <InputText class="form-control" @bind-Value="user.Name" id="userName"></InputText> </label> <br/> <label for="userEmail" class="form-label"> Введите email <InputText class="form-control" @bind-Value="user.Email" id="userEmail"></InputText> </label> <br/> <label for="userAge" class="form-label"> Введите возраст <InputNumber class="form-control" @bind-Value="user.Age" id="userAge"></InputNumber> </label> <br/> <label for="userRole" class="form-label"> Введите роль <InputText class="form-control" @bind-Value="user.Role" id="userRole"></InputText> </label> <br /> <button type="submit" class="btn btn-primary">Отправить</button> </EditForm> @code { User user = new User(); private void OnSubmit() { using (var db = DbFactory.CreateDbContext()) { user.CreatedAt = DateTime.Now; db.Users.Add(user); db.SaveChanges(); } _navigation.NavigateTo("/"); } }
В HTML-разметке компонента мы определили форму для создания нового пользователя. В запущенном приложении она будет выглядеть следующим образом:
В C#-коде компонента мы обращаемся к фабрике контекстов, создаем новый контекст данных приложения и добавляем нового пользователя в базу данных:
@code { User user = new User(); private void OnSubmit() { using (var db = DbFactory.CreateDbContext()) { user.CreatedAt = DateTime.Now; db.Users.Add(user); db.SaveChanges(); } _navigation.NavigateTo("/"); } }
Для того, чтобы использовать в нашем компоненте фабрику контекстов и навигацию, мы использовали директиву inject
для внедрения в компонент соответствующих сервисов:
@inject NavigationManager _navigation @inject IDbContextFactory<ApplicationContext> DbFactory
Чтобы объект нового пользователя был добавлен в базу данных (чтобы EF Core сгенерировала необходимый запрос INSERT и выполнила его) мы вызвали метод:
db.SaveChanges();
Аналогичным образом в EF Core работают другие методы Add...
, только AddRange
позволяют создавать сразу не одну, а несколько записей объектов в базе данных.
Чтение записей (Read)
С чтением записей из базы данных мы уже сталкивались не раз, когда разрабатывали наше приложение. Для чтения записей из базы данных используются методы, начинающиеся с To.
.. — ToList
, ToArray
, ToDictionary
и ToLookup
. Например, перепишем компонент Index.razor таким образом, чтобы при создании компонента все записи из таблицы users считывались в массив User[]
:
@using EFCoreBlazorMigrations.Models @using EFCoreBlazor; @inject IDbContextFactory<ApplicationContext> DbFactory <PageTitle>Index</PageTitle> @if (!loading) { <table class="table"> <thead> <tr> <th scope="col">#</th> <th scope="col">Name</th> <th scope="col">Age</th> <th scope="col">Email</th> </tr> </thead> <tbody> @foreach (User user in users) { <tr> <th scope="row">@user.Id</th> <td>@user.Name</td> <td>@user.Age</td> <td>@user.Email</td> </tr> } </tbody> </table> <a href="/adduser" class="btn btn-primary">Добавить пользователя</a> } @code { bool loading = true; User[] users; protected override void OnInitialized() { using (var db = DbFactory.CreateDbContext()) { loading = true; users = db.Users.ToArray(); } loading = false; } }
Теперь при запуске приложения мы сразу увидим список пользователей, находящихся в данный момент в базе данных:

Обновление данных (Update)
Для обновления данных в EF Core могут использоваться методы DbSet<TEntity>.Update
и DbSet<TEntity>.UpdateRange
. Чтобы обновить какую-либо запись в базе данных объект модели в C# должен обязательно содержать значение свойства Id
. Чтобы продемонстрировать обновление данных в EF Core, создадим в нашем приложении ещё один компонент (назовем его UpdateUser.razor):
@page "/updateuser/{id:int}" <h3>Обновление пользователя</h3> @using EFCoreBlazorMigrations.Models @using Microsoft.EntityFrameworkCore @using EFCoreBlazor; @inject NavigationManager _navigation @inject IDbContextFactory<ApplicationContext> DbFactory @if (loading) { <p>Получаем данные о пользователе...</p> } else { <EditForm Model="user" OnSubmit="UpdateUserData"> <label for="userName" class="form-label"> Имя <InputText class="form-control" @bind-Value="user.Name" id="userName"></InputText> </label> <br/> <label for="userEmail" class="form-label"> Email <InputText class="form-control" @bind-Value="user.Email" id="userEmail"></InputText> </label> <br/> <label for="userAge" class="form-label"> Возраст <InputNumber class="form-control" @bind-Value="user.Age" id="userAge"></InputNumber> </label> <br/> <label for="userRole" class="form-label"> Роль <InputText class="form-control" @bind-Value="user.Role" id="userRole"></InputText> </label> <br /> <button type="submit" class="btn btn-primary">Обновить</button> </EditForm> } @code { [Parameter] public int Id { get; set; } bool loading = true; User? user; protected override void OnParametersSet() { using (var db = DbFactory.CreateDbContext()) { loading = true; user = db.Users.Where(f => f.Id == Id).FirstOrDefault(); loading = user == null; } } private void UpdateUserData() { using (var db = DbFactory.CreateDbContext()) { loading = true; if (user != null) { db.Users.Update(user); db.SaveChanges(); _navigation.NavigateTo("/"); } else throw new Exception("Ошибка обновления данных пользователя"); } loading = false; } }
Что касается html-разметки компонента, то она будет точно такая же как и при добавлении нового пользователя (см. выше). Первое на что стоит обратить внимание — это то, что в адресе страницы компонента мы передаем параметр:
@page "/updateuser/{id:int}"
который будет содержать id пользователя данные которого мы будем обновлять. Для хранения значения этого параметра, в коде компонента определено свойство:
[Parameter] public int Id { get; set; }
Также, в компоненте переопределен метод компонента OnParametersSet
protected override void OnParametersSet() { using (var db = DbFactory.CreateDbContext()) { loading = true; user = db.Users.Where(f => f.Id == Id).FirstOrDefault(); loading = user == null; } }
здесь мы получаем из базы данных запись пользователя. Для этого мы используем методы расширения LINQ:
user = db.Users.Where(f => f.Id == Id).FirstOrDefault();
Если пользователь с Id
, полученным в параметрах, не будет найден в базе, то на экране мы увидим только надпись «Получаем данные о пользователе…» и форма редактирования не будет показана. Если же пользователь будет найден в БД, то загрузится форма для редактирования данных, а клик по кнопке формы «Обновить» вызовет метод:
private void UpdateUserData() { using (var db = DbFactory.CreateDbContext()) { loading = true; if (user != null) { db.Users.Update(user); db.SaveChanges(); _navigation.NavigateTo("/"); } else throw new Exception("Ошибка обновления данных пользователя"); } loading = false; }
Здесь мы также создаем контекст данных, используя фабрику и последовательно вызываем методы EF Core —
db.Users.Update(user)
обновляем данные пользователя
db.SaveChanges();
сохраняем изменения в базе данных.
После этого, мы перемещаемся на главную страницу приложения:
_navigation.NavigateTo("/");
Удаление записей (Delete)
Для удаления данных в EF Core могут использоваться методы DbSet<TEntity>.Remove
и DbSet<TEntity>.RemoveRange
. Чтобы удалить запись из БД нам не потребуется создания нового компонента в базе данных. Просто допишем наш компонент Index.razor следующим образом:
@page "/" @using Microsoft.EntityFrameworkCore @using EFCoreBlazorMigrations.Models @using EFCoreBlazor; @inject IDbContextFactory<ApplicationContext> DbFactory <PageTitle>Index</PageTitle> @if (!loading) { <table class="table"> <thead> <tr> <th scope="col">#</th> <th scope="col">Name</th> <th scope="col">Age</th> <th scope="col">Email</th> <th scope="col">Управление</th> </tr> </thead> <tbody> @foreach (User user in users) { <tr> <th scope="row">@user.Id</th> <td>@user.Name</td> <td>@user.Age</td> <td>@user.Email</td> <td> <div class="btn-group" role="group" aria-label="rules"> <a href="/updateuser/@user.Id" class="btn btn-info">Редактировать</a> <button class="btn btn-danger" @onclick="(d=>DeleteUser(user.Id))">Удалить</button> </div> </td> </tr> } </tbody> </table> <a href="/adduser" class="btn btn-primary">Добавить пользователя</a> } @code { bool loading = true; User[] users = null!; protected override void OnInitialized() { using (var db = DbFactory.CreateDbContext()) { loading = true; users = db.Users.ToArray(); } loading = false; } private void DeleteUser(int id) { using (var db = DbFactory.CreateDbContext()) { loading = true; User? user = users.Where(f => f.Id == id).FirstOrDefault(); if (user != null) { db.Users.Remove(user); db.SaveChanges(); users = db.Users.ToArray(); } else { throw new Exception("Ошибка удаления пользователя"); } } loading = false; } }
Во-первых, для каждой записи пользователя мы добавили в HTML-разметке компонента новую ячейку:
<div class="btn-group" role="group" aria-label="rules"> <a href="/updateuser/@user.Id" class="btn btn-info">Редактировать</a> <button class="btn btn-danger" @onclick="(d=>DeleteUser(user.Id))">Удалить</button> </div>
в которой будут отображаться элементы управления пользователем — команды «Редактировать» и «Удалить».
Во-вторых, в коде компонента мы добавили метод, с помощью которого мы будем удалять запись о пользователе:
private void DeleteUser(int id) { using (var db = DbFactory.CreateDbContext()) { loading = true; User? user = users.Where(f => f.Id == id).FirstOrDefault(); if (user != null) { db.Users.Remove(user); db.SaveChanges(); users = db.Users.ToArray(); } else { throw new Exception("Ошибка удаления пользователя"); } } loading = false; }
Если пользователь найден в списке users
, то вызываются методы
db.Users.Remove(user); db.SaveChanges();
а затем, список пользователей обновляется, что приводит к перерисовке компонента. В запущенном приложении страница приложения будет выглядеть следующим образом:
Итого
Скачать код проекта из Github
Сегодня мы рассмотрели основные синхронные операции с данными в EF Core. Кроме этого, в EF Core также могут применяться асинхронные операции AddAsync
, AddRangeAsync
. Чтобы использовать асинхронный API EF Core, для сохранения данных в базе нам необходимо использовать асинхронный метод SaveChangesAsync
. В остальном же, использование асинхронных методов ничем не отличается от синхронных (учитывая, конечно же, в принципе особенности асинхронного программирования в C#). На этом, можно закончить вводную часть по EF Core и перейти к более детальному рассмотрению вопросов, связанных с работой EF Core в C#.