Содержание
До этого момента мы, в основном, занимались настройкой нашего приложения для работы с базой данных 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#.
