使用实体框架核心的Blazor并发问题 [英] Blazor concurrency problem using Entity Framework Core

查看:195
本文介绍了使用实体框架核心的Blazor并发问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的目标

我想创建一个新的IdentityUser并显示已经通过同一Blazor页面创建的所有用户.该页面具有:

I want to create a new IdentityUser and show all the users already created through the same Blazor page. This page has:

  1. 通过您创建的表单将创建一个IdentityUser
  2. 第三方的网格组件(DevExpress Blazor DxDataGrid),该组件显示使用UserManager.Users属性的所有用户.该组件接受IQueryable作为数据源.

问题

通过表单(1)创建新用户时,将出现以下并发错误:

When I create a new user through the form (1) I will get the following concurrency error:

InvalidOperationException:在上一个操作完成之前,第二个操作在此上下文上开始.不保证任何实例成员都是线程安全的.

InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread-safe.

我认为问题与 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.

重现问题的步骤

  1. 使用身份验证创建新的Blazor服务器端项目
  2. 使用以下代码更改Index.razor:

  1. create a new Blazor server-side project with authentication
  2. 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 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)

在这里您可以找到

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

[...] 这种解耦对于建议使用IDbContextFactory的Blazor应用程序非常有用,但在其他情况下也可能有用.

[...] This decoupling is very useful for Blazor applications, where using IDbContextFactory is recommended, but may also be useful in other scenarios.

如果您有兴趣,可以在宣布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:

InvalidOperationException:在上一个操作完成之前,第二个操作在此上下文上开始.不保证任何实例成员都是线程安全的.

InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread-safe.

关于此问题,有两个解决方法:

  • (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.
    • 无论如何,每个解决方案都能工作,因为它们创建了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:

      • 两个拥有相同UserManager<TUser>实例的组件,其中包含一个临时DbContext.
      • two components that own the same UserManager<TUser> instance which contains a transient DbContext.

      当前,我通过另一种解决方法解决了这个问题:

      Currently, I solved this problem with another workaround:

      • I don't use UserManager<TUser> directly instead, I create a new instance of it through IServiceProvider and then it works. I am still looking for a method to change the UserManager's lifetime instead of using IServiceProvider.

      提示:注意服务的使用寿命

      tips: pay attention to services' lifetime

      这是我学到的.我不知道这是否正确.

      This is what I learned. I don't know if it is all correct or not.

      这篇关于使用实体框架核心的Blazor并发问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆