Blazor concurrency problem using Entity Framework Core

asp.net-core blazor blazor-server-side entity-framework-core

Question

My goal

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

  1. a form through you will create an IdentityUser
  2. a third-party's grid component (DevExpress Blazor DxDataGrid) that shows all users using UserManager.Users property. This component accepts an IQueryable as a data source.

Problem

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

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

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.

Step to reproduce the problem

  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()}" });
        }
    }
    

What I noticed

  • If I change Entity Framework provider from SqlServer to Sqlite then the error will never show.

System info

  • ASP.NET Core 3.1.0 Blazor Server-side
  • Entity Framework Core 3.1.0 based on SqlServer provider

What I have already seen

Why I want to use IQueryable

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.

1
4
1/15/2020 9:02:33 AM

Popular Answer

General solution

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 as the lifetime of each component. 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: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread-safe.

There are two workaround about this problem:

  • (A) set DbContext's lifetime to Transient
services.AddDbContext<ApplicationDbContext>(opt =>
    opt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);
  • (B) as Carl Franklin suggested (after my question): create a singleton service with a static method which returns a new instance of DbContext.

anyway, each solution works because they create a new instance of DbContext.

About my problem

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 or change the Blazor page. This UserManager<TUser> is inside two components on the same page. Then we have the same problem described before:

  • two components that own the same UserManager<TUser> object which contains a transient DbContext.

Currently, I solved this problem with another workaround:

tips: pay attention to services' lifetime

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

1
3/30/2020 8:57:39 PM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow