Первое приложение Blazor Server с EF Core

В предыдущей части мы разработали простое консольное приложение с EF Core для работы с MySQL и посмотрели как EF Core упрощает нам разработку, беря на себя рутинные операции по работе с базой данных. Однако, что касается использования EF Core в Blazor Server, то здесь необходимо использовать несколько иной подход к внедрению возможностей EF Core в приложение. И сегодня мы рассмотрим эти особенности, чтобы в дальнейшем уже полностью сосредоточиться на работе с EF Core, не возвращаясь к вопросам настройки и внедрения EF Core в приложение.

Blazor Server — это платформа приложений с отслеживанием состояния. Для уникальной модели приложения, которую предоставляет Blazor Server, требуется специальный подход к использованию Entity Framework Core.

Проблемы использования EF Core в Blazor Server

Первая и основная проблема использования EF Core в приложениях Blazor Server заключается в том, что DbContext, который мы используем для создания своего контекста данных, не является потокобезопасным и не предназначен для одновременного использования. Использование же метода расширения EF Core AddDbContext в приложениях ASP.NET Core регистрируют контекст в качестве службы с областью Scoped, что также может вызывать проблемы в Blazor Server. Вот, что говорят разработчики Blazor о проблемах сервисов с различными жизненными циклами, использующих EF Core:

  • Singleton. В этом случае, объект сервиса создается при первом обращении к нему и все последующие запросы используют один и тот же ранее созданный объект сервиса, что приводит к неприемлемому одновременному использованию DbContext.
  • Scoped.  Приводит к той же проблеме для компонентов одного и того же пользователя. В этом случае, экземпляр контекста данных является общим для компонентов в канале пользователя.
  • Transient . В этом случае, при каждом запросе создается новый экземпляр, но, поскольку компоненты Blazor могут существовать достаточно долго, то это может приводить к более долгоживущему контексту, чем предполагалось изначально.

Исходя из представленных выше проблем использования в Blazor Server EF Core в качестве сервиса, рекомендуемым подходом для создания нового экземпляра DbContext  является использование фабрики. EF Core 5.0 или более поздней версии предоставляет встроенную фабрику для создания новых контекстов. Рассмотрим этот подход на примере БД MySQL, которую мы создавали в предыдущей части руководства.

Первое приложение Blazor Server с EF Core

Создадим новое приложение Blazor Server:

Также, добавим в проект необходимые пакеты для работы с EF Core и MySQL, то есть Microsoft.EntityFrameworkCore и Pomelo.EntityFrameworkCore.MySql.

Теперь настроим наше приложение, используя файл конфигурации appsettings.json. На данный момент это файл у нас выглядит следующим образом:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Добавим в этот файл строку подключения к БД MySQL следующим образом:

{
  "ConnectionStrings": {
    "DefaultConnection": "server=127.0.0.1;Port=3306;user=admin;password=test;database=users;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Теперь добавим в приложение модель и контекст данных, как мы это делали в консольном приложении. Модель данных:

namespace EFCoreBlazor.Models
{
    public class User
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public string? Email { get; set; }
        public int Age { get; set; }
    }
}

Контекст данных:

using Microsoft.EntityFrameworkCore;
using EFCoreBlazor.Models;

namespace EFCoreBlazor
{
    public class ApplicationContext: DbContext
    {
        public DbSet<User> Users { get; set; }

        public ApplicationContext(DbContextOptions<ApplicationContext> options):base(options) 
        {
            _ = Database.EnsureCreated();
        }
    }
}

Здесь стоит обратить внимание на конструктор класса ApplicationContext. В конструктор класса через параметр options передаются настройки контекста данных, в то время, как в консольном приложении мы переопределяли метод OnConfiguring базового класса.

Следующий шаг — создание фабрики контекстов данных в нашем приложении.

Создание фабрики контекстов

Перейдем в файл Program.cs и добавим в метод Main следующим код:

public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            .....
            //считываем из appsettings.json строку подключения
            string? connection = builder.Configuration.GetConnectionString("DefaultConnection");
            //создаем фабрику
            if (connection != null)
            {
                builder.Services.AddDbContextFactory<ApplicationContext>(opt => opt.UseMySql(connection, ServerVersion.AutoDetect(connection)));
            }

Здесь мы, во-первых, прочитали из файла конфигурации строку подключения к MySQL и сохранили её значение в переменную connection. Во-вторых, мы добавили в наш проект фабрику контекстов:

builder.Services.AddDbContextFactory<ApplicationContext>(opt => opt.UseMySql(connection, ServerVersion.AutoDetect(connection)));

здесь мы определили тип контекста данных, который будет создаваться фабрикой — ApplicationContext и указали необязательный параметр — действие (Action) для настройки подключения. То есть, по сути, мы использовали альтернативный вариант настройки контекста вместо использования переопределенного метода OnConfiguring. Теперь мы можем воспользоваться этой фабрикой в нашем приложении для работы с БД MySQL.

Использование фабрики контекстов в Blazor Server

Так как мы зарегистрировали фабрику в качестве сервиса, то мы можем воспользоваться ей в любом компоненте Blazor, используя директиву @inject. Например, перепишем код компонента Index.razor следующим образом:

@page "/"

@using Microsoft.EntityFrameworkCore
@using EFCoreBlazor.Models

@inject IDbContextFactory<ApplicationContext> DbFactory

<PageTitle>Index</PageTitle>

@if (loading)
{
    <button type="button" class="btn btn-danger" @onclick="@LoadData">Загрузить данные</button>
}
else
{
    <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>
}


@code {
    bool loading = true;

    List<User> users;

    private void LoadData()
    {
        using (var db = DbFactory.CreateDbContext())
        {
            loading = true;
            users = db.Users.ToList();
        }
        loading = false;
    }
}

Рассмотрим, что происходит в этом компоненте. Во-первых, мы подключили необходимые пространства имен:

@using Microsoft.EntityFrameworkCore 
@using EFCoreBlazor.Models

Далее, мы внедрили в компонент сервис фабрики контекстов:

@inject IDbContextFactory<ApplicationContext> DbFactory

Теперь посмотрим на код C# компонента. Здесь мы определили переменную loading, которая выступает для нас неким флагом сигнализирующим о том, загружаются ли данные в текущий момент времени или нет. По умолчанию, считаем, что данные загружаются

bool loading = true;

далее, мы определили список в котором будем хранить сущности из БД:

List<User> users;

В методе LoadData мы выгружаем из БД список всех пользователей:

private void LoadData()
{
    using (var db = DbFactory.CreateDbContext())
    {
        loading = true;
        users = db.Users.ToList();
    }
    loading = false;
}

используя метод фабрики CreateDbContext мы создаем контекст данных, который будем использовать в компоненте, поднимаем флаг loading, считываем список пользователей и опускаем флаг, сигнализируя тем самым компоненту о том, что можно выводить список на экран. В HTML-разметке компонента мы отслеживаем состояние флага и либо рисуем кнопку «Загрузить данные», либо выводим таблицу с пользователями на экран.

Вот как будет выглядеть приложение при запуске:

А так, после нажатия кнопки:

На этом этапе база MySQL содержит данные, которые были добавлены при создании консольного приложения

Начальная конфигурация

В консольном приложении для начальной конфигурации мы проверяли наличие сущностей в наборе DbSet<User>. Еще один из вариантов начальной конфигурации БД — это переопределение базового метода OnModelCreating в контексте данных. Вот как мы можем его переопределить:

public class ApplicationContext: DbContext
{
   ....

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().HasData(new User() {Id=1, Name="Джон Сноу", Age=35, Email="winterfall@throne.com"});
    }
}

С помощью метода HasData мы добавляем нового пользователя в БД, если таблица users будет пуста. При этом, стоит обратить внимание, что при таком способе начальной конфигурации мы обязательно указываем для модели значение свойства Id (первичного ключа).

Теперь, если удалить базу данных и снова запустить приложение, то EF Core создаст базу данных и добавит в таблицу users одного нового пользователя. При повторном запуске приложения пользователь не будет добавляться автоматически.

Итого

Скачать код проекта из Github

Итак, мы разработали первое приложение Blazor Server с EF Core для работы с базой MySQL, учитывая при этом особенности Blazor и рекомендации команды разработчиков Microsoft. В отличие от других типов проектов ASP.NET, в Blazor мы используем фабрику контекстов данных для создания и использования контекста в компоненте Blazor.

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