Blazor и ядро ​​EF — разные потоки одновременно используют один и тот же экземпляр DbContext

Blazor и ядро ​​EF — разные потоки одновременно используют один и тот же экземпляр DbContext — проблема


Я пытаюсь перейти к компоненту Blazor, но компонент загружается дважды.

@page "/MySettings"
@using eKurser_Blazor.Data.Services;
@using eKurser_Blazor.DataDB;
@inject UserService UserService
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>Get profile</h1>

@if (ErrorMessage != null)
{
    <div class = "alert alert-danger">@ErrorMessage</div>
}

<form>
    <div class = "form-group">
        <label for = "firstName">Fname</label>
        <input type = "text" class = "form-control" id = "firstName" @bind = "@Fname" @value = "@Fname">
    </div>
    <div class = "form-group">
        <label for = "lastName">Lname</label>
        <input type = "text" class = "form-control" id = "lastName" @bind = "@Lname" @value = "@Lname">
    </div>
    <div class = "form-group">
        <label for = "email">E-mail</label>
        <input type = "email" class = "form-control" id = "email" @bind = "@Email" @value = "@Email">
    </div>

</form>

Этот код будет выполнен дважды.

protected override async Task OnInitializedAsync()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;
    string userIdClaim = user.FindFirst("UserID")?.Value;

    if (!string.IsNullOrEmpty(userIdClaim))
    {
        var _user = await UserService.GetUserByIdAsync(userIdClaim);
        if (_user != null)
        {

        }
    }
}

Я изменил _Host.cshtml в соответствии с:

<component type = "typeof(App)" render-mode = "Server" />

Blazor дважды отрисовывает содержимое

Но я закончу с ошибкой:

Ошибка: System.InvalidOperationException: уже существует открытое DataReader, связанное с этим соединением, которое необходимо сначала закрыть. в Microsoft.Data.SqlClient.SqlCommand.<>c.b__208_0(Task1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask2.InnerInvoke() в System.Threading.ExecutionContext.RunInternal (ExecutionContext executeContext, обратный вызов ContextCallback, состояние объекта)

UserService.cs

public class UserService
    {
        private readonly MyDbContext _dbContext;

        public UserService(MyDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<TblUser> AuthenticateAsync(string email, string password)
        {
            if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
            {
                return null;
            }

            TblUser user = null;

            try
            {
                user = await _dbContext.TblUsers.SingleOrDefaultAsync(x => x.Email == email);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
                return null;
            }

            if (user == null)
            {
            }

            if (user == null || !PasswordHash.ValidatePassword(password, user.Pwd))
            {
                return null;
            }

            return user;
        }
        public async Task<TblUser> GetUserByIdAsync(string userId)
        {
            if (string.IsNullOrEmpty(userId))
            {
                return null;
            }

            var user = await _dbContext.TblUsers.SingleOrDefaultAsync(x => x.UserId == userId);
            return user;
        }      

        private bool UserExists(string userId)
        {
            return _dbContext.TblUsers.Any(e => e.UserId == userId);
        }

Программа.cs

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<UserService>();

builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthenticationStateProvider>());

var connString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContextFactory<MyDbContext>((DbContextOptionsBuilder option
    ) => option.UseSqlServer(connString));

// Add services to the container.
builder.Services.AddControllersWithViews();

Если я вызову метод вручную после загрузки компонента, проблем не возникнет, но я, конечно, хотел бы загружать и представлять данные так же автоматически, как загружается компонент.

 <button type = "submit" class = "btn btn-primary" @onclick = "GetUserInfo">Ladda</button>

   @code {
    private async Task GetUserInfo()
    {
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;
        string userIdClaim = user.FindFirst("UserID")?.Value;

        if (!string.IsNullOrEmpty(userIdClaim))
        {
            var _user = await UserService.GetUserByIdAsync(userIdClaim);
            if (_user != null)
            {

            }
        }
    }
}

ОБНОВЛЕНИЕ 1

Отредактированный UserService.cs

private readonly IDbContextFactory<MyDbContext> _factory;

public UserService(IDbContextFactory<MyDbContext> factory)
{
    _factory = factory;
}

Это устранило ошибку, но компонент по-прежнему дважды срабатывает OnInitializedAsync(). И это покажет данные в первый раз, но после перехода на другую страницу и обратно var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); будет пустым.

Обновление 2

Проблема отсутствия данных при навигации или обновлении заключалась в неправильной реализации сохранения AuthenticationState. Я установил Blazored.LocalStorage, чтобы сохранить состояние в localStorage. OnInitializedAsync() по-прежнему срабатывает дважды

Обновление 3

Я не совсем уверен, что я сделал, но исправление AuthenticationState и перезапуск VS решили мою проблему!

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
126
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваши проблемы могут быть не связаны напрямую.

Давайте сначала разберемся с проблемой DbContext.

Вы пытаетесь использовать один и тот же DbContext в двух операциях одновременно. Это распространенная проблема в асинхронных операциях. У вас уже есть IDbContextFactory, загруженный в DI, вам просто нужно начать использовать его для создания контекстов транзакций/единиц работы.

Вот ваш UserService:

public class UserService
    {
        private readonly IDbContextFactory _factory;

        public UserService(IDbContextFactory factory)
        {
            _factory = factory;
        }
        //...

        public async ValueTask SomeWorkAsync()
        {
            using var dbContext = _factory.CreateDbContext();

            // await do something with dbContext
        }
    }

При двойной загрузке есть три возможные причины, по которым OnInitializedAsync загружается дважды:

  1. Вы перезагружаете SPA, или это ваша стартовая страница.
  2. Вы вызываете его вручную.
  3. Вы загружаете более одного экземпляра компонента.

Куда вы направляетесь MySettings и как?

Обновлять

Вам нужно установить, загружается ли код дважды или вы загружаете два разных компонента.

Добавьте это в MySettings и проверьте

@code {
    public readonly Guid ComponentUid = Guid.NewGuid();

    protected override void OnInitialized()
    {
        Debug.WriteLine($"XXX OnInitialized called on Component Uid = {ComponentUid.ToString()}");
    }

    protected override Task OnInitializedAsync()
    {
        Debug.WriteLine($"XXX OnInitializedAsync called on Component Uid = {ComponentUid.ToString()}");
        //...
    }

}

И проверьте вывод.

Переход осуществляется из NavMenu.razor с помощью <NavLink class = "nav-link" href = "MySettings"> <span class = "oi oi-list-rich" aria-hidden = "true"></span> Min Sida </ навигационная ссылка>

Jonas Johansson 06.04.2023 10:42

Обновил мой пост. Как я могу продолжить и выяснить, как избежать двойного запуска загрузки OnInitializedAsync в моем компоненте?

Jonas Johansson 06.04.2023 11:36

Вы устранили проблему? Смотрите мой обновленный ответ, который показывает вам, как проверить последовательность загрузки.

MrC aka Shaun Curtis 06.04.2023 15:04

Это решено, смотрите мой обновленный пост

Jonas Johansson 06.04.2023 17:53

Другие вопросы по теме