Blazor and EF core - Different threads concurrently using the same instance of DbContext - Problem
I'm trying to navigate to a Blazor component but the component loads twice.
@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>
This code will be executed twice.
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)
{
}
}
}
I have changed _Host.cshtml according to:
<component type="typeof(App)" render-mode="Server" />
Blazor rendering content twice
But I will end up with error:
Error: System.InvalidOperationException: There is already an open DataReader associated with this Connection which must be closed first. at Microsoft.Data.SqlClient.SqlCommand.<>c.b__208_0(Task 1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask
2.InnerInvoke() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
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);
}
Program.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();
If I Invoke the Method manually after the Component is loaded there is no problem, but I would of course like to load and present the data as automatically as the component is loaded.
<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)
{
}
}
}
}
UPDATE 1
Edited UserService.cs
private readonly IDbContextFactory<MyDbContext> _factory;
public UserService(IDbContextFactory<MyDbContext> factory)
{
_factory = factory;
}
This removed the error, but the component still triggers OnInitializedAsync()
twice. And this will show data first time, but after navigating to another page and back, var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
is empty.
Update 2
The issue for missing data on navigation or update was the incorrect implementation of saving the AuthenticationState. I installed Blazored.LocalStorage to save the State in localStorage. OnInitializedAsync()
is still triggered twice
Update 3
I'm not really sure what I did but fixing the AuthenticationState and restarting VS solved my issue!
1条答案
按热度按时间hrirmatl1#
Your problems may not be directly related.
Let's deal with the DbContext problem first.
You are attempting to use the same
DbContext
in two operations at the same time. This is a common problem in asynchronous operations. You already have theIDbContextFactory
loaded into DI, you just need to start using it to create transactional/unit of work contexts.Here's your
UserService
:On loading twice, there are three probable reasons
OnInitializedAsync
is loading twice:Where are you navigating to
MySettings
and how?Update
You need to establish if the code is loading twice or you are loading two different components.
Add this to
MySettings
and check theAnd check the Output.