我的目標
我想創建一個新的IdentityUser並顯示已通過同一Blazor頁面創建的所有用戶。該頁面具有:
問題
通過表單(1)創建新用戶時,將出現以下並發錯誤:
InvalidOperationException:在上一個操作完成之前,第二個操作在此上下文上啟動。不保證任何實例成員都是線程安全的。
我認為問題與CreateAsync(IdentityUser user)和UserManager.Users引用相同的DbContext有關
該問題與第三方組件無關,因為我重現了相同的問題,用一個簡單的列表替換了它。
重現問題的步驟
使用以下代碼更改Index.razor:
@page "/"
<h1>Hello, world!</h1>
number of users: @Users.Count()
<button @onclick="@(async () => await Add())">click me</button>
<ul>
@foreach(var user in Users)
{
<li>@user.UserName</li>
}
</ul>
@code {
[Inject] UserManager<IdentityUser> UserManager { get; set; }
IQueryable<IdentityUser> Users;
protected override void OnInitialized()
{
Users = UserManager.Users;
}
public async Task Add()
{
await UserManager.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" });
}
}
我注意到的
系統信息
我已經看過的
為什麼我要使用IQueryable
我想將IQueryable作為我的第三方組件的數據源傳遞,因為它可以將分頁和過濾直接應用於查詢。此外,IQueryable對CUD操作敏感。
我問了Daniel Roth BlazorDeskShow-2:24 :20這個問題,從設計上看這似乎是Blazor 服務器端的問題。 DbContext默認生存期設置為Scoped
作為每個組件的生存期。因此,如果您在同一頁面中至少有兩個試圖執行異步查詢的組件,那麼我們將遇到異常:
InvalidOperationException:在上一個操作完成之前,第二個操作在此上下文上啟動。不保證任何實例成員都是線程安全的。
關於此問題,有兩種解決方法 :
services.AddDbContext<ApplicationDbContext>(opt =>
opt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);
DbContext
的新實例。 無論如何,每個解決方案都能工作,因為它們創建了DbContext
的新實例。
我的問題並非與DbContext
嚴格相關,而是與UserManager<TUser>
具有一定Scoped
生存期。將DbContext的生存期設置為Transient
並不能解決我的問題,因為當我第一次打開會話時,ASP.NET Core會創建UserManager<TUser>
的新實例,並且它一直存在,直到我不關閉它或更改Blazor頁面為止。此UserManager<TUser>
在同一頁面上的兩個組件內。然後,我們遇到了之前描述的相同問題:
UserManager<TUser>
對象的組件,該對象包含一個臨時DbContext
。 目前,我通過另一種解決方法解決了這個問題:
UserManager<TUser>
,而是通過IServiceProvider
創建它的新實例,然後它可以工作。 我仍在尋找一種方法來更改UserManager的生存期,而不是使用IServiceProvider
。 提示:注意服務的生命
這是我學到的。我不知道這是否正確。