问题描述
我的目标
我想创建一个新的IdentityUser并显示已经通过同一Blazor页面创建的所有用户.该页面具有:
I want to create a new IdentityUser and show all the users already created through the same Blazor page. This page has:
- 通过您创建的表单将创建一个IdentityUser
- 第三方的网格组件(DevExpress Blazor DxDataGrid),该组件显示使用UserManager.Users属性的所有用户.该组件接受IQueryable作为数据源.
问题
通过表单(1)创建新用户时,将出现以下并发错误:
When I create a new user through the form (1) I will get the following concurrency error:
我认为问题与 CreateAsync(IdentityUser用户)和 UserManager.Users 引用相同的DbContext
I think the problem is related to the fact that CreateAsync(IdentityUser user) and UserManager.Users are referring the same DbContext
该问题与第三方组件无关,因为我重现了相同的问题,用一个简单的列表替换了它.
The problem isn't related to the third-party's component because I reproduce the same problem replacing it with a simple list.
重现问题的步骤
- 使用身份验证创建新的Blazor服务器端项目
-
使用以下代码更改Index.razor:
- create a new Blazor server-side project with authentication
change Index.razor with the following code:
@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()}" });
}
}
我注意到的
- 如果我将Entity Framework提供程序从SqlServer更改为Sqlite,则错误将永远不会显示.
系统信息
- ASP.NET Core 3.1.0 Blazor服务器端
- 基于SqlServer提供程序的实体框架Core 3.1.0
我所看到的
- Blazor秒操作在此操作之前在此上下文上开始:该解决方案对我不起作用,因为即使我将DbContext范围从 Scoped 更改为 Transient 我仍在使用相同的UserManager实例,并且它包含相同的DbContext实例
- StackOverflow上的其他人建议为每个请求创建一个新的DbContext实例.我不喜欢这种解决方案,因为它违反了依赖注入原则.无论如何,我无法应用此解决方案,因为DbContext包装在UserManager中
- 创建DbContext的生成器:此解决方案与上一个解决方案非常相似.
- 与Blazor一起使用实体框架核心
- Blazor A second operation started on this context before a previous operation completed: the solution proposed doesn't work for me because even if I change my DbContext scope from Scoped to Transient I still using the same instance of UserManager and its contains the same instance of DbContext
- other guys on StackOverflow suggests creating a new instance of DbContext per request. I don't like this solution because it is against Dependency Injection principles. Anyway, I can't apply this solution because DbContext is wrapped inside UserManager
- Create a generator of DbContext: this solution is pretty like the previous one.
- Using Entity Framework Core with Blazor
为什么要使用IQueryable
我想将IQueryable作为第三方组件的数据源传递,因为它可以将分页和过滤直接应用于查询.此外,IQueryable对CUD敏感 操作.
I want to pass an IQueryable as a data source for my third-party's component because its can apply pagination and filtering directly to the Query. Furthermore IQueryable is sensitive to CUD operations.
推荐答案
更新(2020年8月19日)
UPDATE (08/19/2020)
在这里您可以找到有关如何一起使用Blazor和EFCore的文档
Here you can find the documentation about how to use Blazor and EFCore together
更新(2020年7月22日)
UPDATE (07/22/2020)
EFCore团队在Entity Framework Core .NET 5 Preview 7中引入了DbContextFactory
EFCore team introduces DbContextFactory inside Entity Framework Core .NET 5 Preview 7
如果您有兴趣,可以在宣布Entity Framework Core EF Core 5.0 Preview 7
If you are interested you can read more at Announcing Entity Framework Core EF Core 5.0 Preview 7
更新(2020年6月6日)
UPDATE (07/06/2020)
Microsoft发布了有关Blazor(两种型号)的新的有趣的视频 )和实体框架核心.请看19:20,他们正在谈论如何使用EFCore管理并发问题
Microsoft released a new interesting video about Blazor (both models) and Entity Framework Core. Please take a look at 19:20, they are talking about how to manage concurrency problem with EFCore
我问了丹尼尔·罗斯 BlazorDeskShow-2:24:20 关于此问题,从设计上看,这似乎是Blazor 服务器端的问题.DbContext的默认生存期设置为Scoped
.因此,如果您在同一页面中至少有两个组件正在尝试执行 async 查询,那么我们将遇到异常:
I asked Daniel Roth BlazorDeskShow - 2:24:20 about this problem and it seems to be a Blazor Server-Side problem by design.DbContext default lifetime is set to Scoped
. So if you have at least two components in the same page which are trying to execute an async query then we will encounter the exception:
关于此问题,有两个解决方法:
- (A)将DbContext的生存期设置为 Transient
services.AddDbContext<ApplicationDbContext>(opt =>
opt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);
- (B)如卡尔·富兰克林(Carl Franklin)建议(在我的问题之后):使用静态方法创建单例服务,该方法将返回
DbContext
的新实例. - (B) as Carl Franklin suggested (after my question): create a singleton service with a static method which returns a new instance of
DbContext
. - 两个拥有相同
UserManager<TUser>
实例的组件,其中包含一个临时DbContext
. - two components that own the same
UserManager<TUser>
instance which contains a transientDbContext
. - 我不直接使用
UserManager<TUser>
,而是通过IServiceProvider
创建它的新实例,然后它可以工作. 我仍在寻找一种方法来更改UserManager的生存期,而不是使用IServiceProvider
. - I don't use
UserManager<TUser>
directly instead, I create a new instance of it throughIServiceProvider
and then it works. I am still looking for a method to change the UserManager's lifetime instead of usingIServiceProvider
.
无论如何,每个解决方案都能工作,因为它们创建了DbContext
的新实例.
anyway, each solution works because they create a new instance of DbContext
.
我的问题与DbContext
并不严格相关,但是与UserManager<TUser>
的生存期相同.将DbContext的生存期设置为Transient
并不能解决我的问题,因为当我第一次打开会话时,ASP.NET Core会创建一个新的UserManager<TUser>
实例,并且在不关闭它之前一直存在.此UserManager<TUser>
在同一页面上的两个组件中.然后,我们遇到了之前描述的相同问题:
My problem wasn't strictly related to DbContext
but with UserManager<TUser>
which has a Scoped
lifetime. Set DbContext's lifetime to Transient
didn't solve my problem because ASP.NET Core creates a new instance of UserManager<TUser>
when I open the session for the first time and it lives until I don't close it. This UserManager<TUser>
is inside two components on the same page. Then we have the same problem described before:
当前,我通过另一种解决方法解决了这个问题:
Currently, I solved this problem with another workaround:
这是我学到的.我不知道这是否正确.
This is what I learned. I don't know if it is all correct or not.
这篇关于使用实体框架核心的Blazor并发问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!