aspnetcore: Blazor concurrency problem using Entity Framework Core

My goal:

I want to create a Blazor page with a list of users that it is populated through an IQueryable<IdentityUser> using UserManager<IdentityUser>'s property and a button that create a new user when is clicked.

What did I expect

  1. When the page is loaded I want to see users previously created.
  2. When I click on “click me” button I want to see a new row inside the users’ list

Problem:

When I set my dbContext to SQLServer provider and I click on “click me” button I willl get the following error:

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

What I tried:

  • someone on Stackoverflow suggests changing dbContext scope to transient. I did it but the problem still persists. I think because Create method and IQueryable<IdentityUser> Users are called from the same UserManager’s instance so, same dbContext instance.

Problem sample:

https://github.com/Blackleones/BlazorConcurrencyProblem or create a new ASP.NET Blazor Server Side project with Authentication and change Index.razor page in this way `

@page "/"

<h1>Hello, world!</h1>

number of users: @Users.Count()
<button @onclick="@(async () => await Add())">click me. I work if you use Sqlite</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()}" });
    }
}

`

Extra info about the problem:

  • if I use Sqlite provider then the error will never show.
  • this is a reproduction of a problem that I have inside my project. I’ve seen that there are a few workarounds to avoid concurrency problems, like:
    • set dbContext scope to Transient: I don’t know if it is correct doing it.
    • create a dbContext instance per CRUD request. I would like to avoid this because it is against dependency injection concepts.
  • I’ve already seen this post: Using Entity Framework Core with Blazor #10448 but I didn’t find a solution.
  • someone suggests to not expose IQueryable<T> but return an IList<T> to the user’s interface. I am exposing IQueryable<T> because I am using DevExpress components that they can manage IQueryable<T>. These components can apply filters directly on SQL query if the data source is an IQueryable<T>.

System info:

  • ASP.NET Core 3.1.0 Blazor server side
  • Entity Framework Core 3.1.0

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 20 (6 by maintainers)

Most upvoted comments

Hi, guys, I decide to use this open issue, because I have a similar problem and spent many hours to find a way to solve it but for now is still a problem. For now, I use a workaround solution, creating a new scope for every action to get a fresh database. A server-side scenario with “OwningComponentBase” according to me is not enough. Without “OwningComponentBase” the SignalR connection controls scoped services and there are scoped to this connection. With “OwningComponentBase” scoped services are scoped to the lifetime of the component. If I call a method like GetAllUsersAsync() two times, I will receive the same instance of dbContext. The application is still running and some other user is registering to the system. I call again my get method and because my dbContext is the same as before and I will not see the new user. I must refresh the page or navigate to another page and return after that because somehow I must terminate the component to get a new scope with fresh dbContext. Sorry if the comment is not for here and thank you for your time 😃.

Startup.cs `services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString(“DefaultConnection”))); services.AddDefaultIdentity<IdentityUser>() .AddEntityFrameworkStores<ApplicationDbContext>();

services.AddTransient<EntityService>();

EntityService.cs public EntityService(ApplicationDbContext applicationDbContext) { this.applicationDbContext = applicationDbContext; }`

Here you can find my sample project.

You have to change the connectionString at Startup.cs line 35. Let me know if you will have some problems. Anyway, if you want you can create your sample project following these steps:

  1. create a new Blazor server-side project with authentication
  2. overwrite the index.razor page with the code that you suggest me before
  3. run the project and click on the button that you will find at index.razor

Keep me updated. Thank you.

I’ve filed this doc issue to cover this in docs https://github.com/aspnet/AspNetCore.Docs/issues/16558