Asp.net core 使用实体框架核心的Blazor并发问题
我的目标 我想创建一个新的IdentityUser,并显示通过同一Blazor页面已经创建的所有用户。本页有:Asp.net core 使用实体框架核心的Blazor并发问题,asp.net-core,entity-framework-core,blazor,blazor-server-side,Asp.net Core,Entity Framework Core,Blazor,Blazor Server Side,我的目标 我想创建一个新的IdentityUser,并显示通过同一Blazor页面已经创建的所有用户。本页有: 通过您创建的表单将创建一个IdentityUser 显示使用UserManager.users属性的所有用户的第三方网格组件(DevExpress Blazor DxDataGrid)。此组件接受IQueryable作为数据源 问题 当我通过表单(1)创建新用户时,将出现以下并发错误: InvalidOperationException:在上一个操作完成之前,在此上下文上启动了第二个操
@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()}" });
}
}
@page/“
你好,世界!
用户数:@users.Count()
点击我
@foreach(用户中的var用户)
{
- @user.UserName
}
@代码{
[注入]用户管理器用户管理器{get;set;}
易读用户;
受保护的覆盖无效OnInitialized()
{
Users=UserManager.Users;
}
公共异步任务添加()
{
等待UserManager.CreateAsync(新标识用户{UserName=$“test{Guid.NewGuid().ToString()}”);
}
}
- 如果我将实体框架提供程序从SqlServer更改为Sqlite,那么错误将永远不会显示
- ASP.NET Core 3.1.0 Blazor服务器端
- 基于SqlServer provider的实体框架Core 3.1.0
- :建议的解决方案对我不起作用,因为即使我将DbContext范围从Scoped更改为Transient,我仍然使用相同的UserManager实例,并且它包含相同的DbContext实例
- StackOverflow上的其他人建议为每个请求创建一个新的DbContext实例。我不喜欢这个解决方案,因为它违背了依赖注入原则。无论如何,我无法应用此解决方案,因为DbContext被包装在UserManager中
- :此解决方案与上一个非常相似
操作。也许不是最好的方法,但将异步方法重写为非异步方法可以解决问题:
public void Add()
{
Task.Run(async () =>
await UserManager.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" }))
.Wait();
}
它确保UI仅在创建新用户后更新
Index.razor的全部代码
@page "/"
@inherits OwningComponentBase<UserManager<IdentityUser>>
<h1>Hello, world!</h1>
number of users: @Users.Count()
<button @onclick="@Add">click me. I work if you use Sqlite</button>
<ul>
@foreach(var user in Users.ToList())
{
<li>@user.UserName</li>
}
</ul>
@code {
IQueryable<IdentityUser> Users;
protected override void OnInitialized()
{
Users = Service.Users;
}
public void Add()
{
Task.Run(async () => await Service.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" })).Wait();
}
}
@page/“
@继承OwningComponentBase
你好,世界!
用户数:@users.Count()
点击我。如果使用Sqlite,我就可以工作
@foreach(Users.ToList()中的var user)
{
- @user.UserName
}
@代码{
易读用户;
受保护的覆盖无效OnInitialized()
{
用户=服务。用户;
}
公共无效添加()
{
Task.Run(async()=>await Service.CreateAsync(新的IdentityUser{UserName=$“test{Guid.NewGuid().ToString()}”).Wait();
}
}
更新(2020年8月19日)
在这里你可以找到
更新(2020年7月22日)
EFCore团队在实体框架Core.NET5预览版7中引入了DbContextFactory
[…]这种解耦对于Blazor应用程序非常有用,建议使用IDbContextFactory,但在其他场景中也可能有用
如果你感兴趣,你可以在
更新(2020年6月7日)
微软发布了一个关于Blazor(两种模型)和实体框架核心的有趣的新版本。请看19:20,他们正在讨论如何使用EFCore管理并发问题
通解 我问Daniel Roth这个问题,从设计上看,这似乎是Blazor服务器端的问题。 DbContext默认生存期设置为
范围
。因此,如果在同一页面中至少有两个组件正在尝试执行异步查询,那么我们将遇到异常:
InvalidOperationException:在上一个操作完成之前,在此上下文上启动了第二个操作。任何实例成员都不能保证线程安全
此问题有两种解决方法:
- (A) 将DbContext的生存期设置为Transient
services.AddDbContext而不是使用IServiceProvider
提示:关注服务的生命周期
这是我学到的。我不知道这一切是否正确。好吧,我有一个非常类似的场景,我的“解决方案”是将所有内容从OnInitializedAsync()移动到
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
//Your code in OnInitializedAsync()
StateHasChanged();
}
{
这似乎解决了,但我不知道该怎么办。我想只需跳过初始化,让组件成功构建,然后我们就可以更进一步了
/******************************更新********************************/
我仍然面临着这个问题,似乎我给出了一个错误的解决方案。当我检查这个时,我把问题弄清楚了。因为我实际上要处理很多组件初始化和dbContext操作。根据@dani_herrera的说法,如果一次有多个组件执行Init,那么问题可能会出现。
当我接受了他的建议,将dbContext服务更改为时,我就摆脱了这个问题 I fo
protected override bool ShouldRender()
{
if (_updatingDb)
{
return false;
}
else
{
return base.ShouldRender();
}
}
try
{
_updatingDb = true;
await DbContext.SaveChangesAsync();
}
finally
{
_updatingDb = false;
StateHasChanged();
}
public async Task Add()
{
await UserManager.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" });
}
//First render
Start: BuildRenderTree
End: BuildRenderTree
//Button clicked
Start: Add
(This is where the `await` occurs`)
Start: BuildRenderTree
Exception thrown
protected override bool ShouldRender() => MayRender;
public async Task Add()
{
MayRender = false;
try
{
await UserManager.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" });
}
finally
{
MayRender = true;
}
}
private SpinLock Lock = new SpinLock();
private async Task<WhatTelerikNeeds> ReadData(SomeFilterFromTelerik filter)
{
bool gotLock = false;
while (!gotLock) Lock.Enter(ref gotLock);
try
{
IUserIdentity result = await ApplyFilter(MyDbContext.Users, filter).ToArrayAsync().ConfigureAwait(false);
return new WhatTelerikNeeds(result);
}
finally
{
Lock.Exit();
}
}