EF Core. Основные операции с данными (CRUD)

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

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии