'Blazor wait for ef core to finish request

So right now I get a

Error: System.InvalidOperationException: A second operation was started on this context before a previous operation completed.

Because blazor doesn't seem to respect the current request.

What I am doing is something like this:

FirstComponent.razor

@inject IService _service; // abstracted service that calls EF

<layout> // layout stuff here
  <SecondComponent /> // second blazor component
</layout>

@code {

  protected override async Task OnInitializeAsync()
  {
     var getSomething = await _service.OnInitializedAsync();
  }

}

SecondComponent.razor

@inject IService _service; // abstracted service that calls EF

@code {

  protected override async Task OnInitializedAsync()
  {
     var getSomething = await _service.GetSomething();
  }

}

So I split my entity in to multiple sub-components for editing it. Now I have one "parent" component that calls all these sub-components.


Edit 1

My IService would look like this.

public interface IService
{
  public Task<Something> GetSomething();
}

internal class Service : IService
{
  private readonly SomethingRepository _repo;

  public Service(SomethingRepository repo)
  {
     _repo = repo;
  }

  public async Task<Something> GetSomething() => _repo.GetAsync();
}

internal SomethingRepository
{
  private readonly AppDbContext _context;

  public SomethingRepository(AppDbContext context)
  {
    _context = context;
  }

  public async Task<Something> GetAsync() => ...;//implementation of whatever
}

I am adding my AppDbContext to the service collection with AddDbContext and my services and repositories with AddScoped



Solution 1:[1]

Add this to your connection string "MultipleActiveResultSets=true"

Solution 2:[2]

For Blazor Server apps you should not use the existing DI lifetimes for your DbContext. Instead create a new one for each request, or scope it to a component. per:

EF Core provides the AddDbContext extension for ASP.NET Core apps that registers the context as a scoped service by default. In Blazor Server apps, scoped service registrations can be problematic because the instance is shared across components within the user's circuit. DbContext isn't thread safe and isn't designed for concurrent use. The existing lifetimes are inappropriate for these reasons:

  • Singleton shares state across all users of the app and leads to inappropriate concurrent use.
  • Scoped (the default) poses a similar issue between components for the same user.
  • Transient results in a new instance per request; but as components can be long-lived, this results in a longer-lived context than may be intended.

The following recommendations are designed to provide a consistent approach to using EF Core in Blazor Server apps.

  • By default, consider using one context per operation. . . .
  • Use a flag to prevent multiple concurrent operations: . . .
  • For longer-lived operations that take advantage of EF Core's change tracking or concurrency control, scope the context to the lifetime of the component.

ASP.NET Core Blazor Server with Entity Framework Core (EFCore)

Solution 3:[3]

Typically this happens when you use async/await in calling the service but do not use flags to stop the rendering of the child components. This should fix most of your "A second operation started..." issues.

<layout>
@if(_loading)
{
 <span> Loading .... </span>
}
else{
 <ChildComponent/>
}
</layout>


@code
{
  bool _loading;
  protected override async Task OnInitializedAsync(){
     _loading = true;
     var data = _service.getSomething();
     _loading = false;
  }
}

Solution 4:[4]

So using the factory pattern provided by Henk Holterman is not a solution for me. See: https://docs.microsoft.com/en-us/ef/core/dbcontext-configuration/#using-a-dbcontext-factory-eg-for-blazor

That did work is changing everythings lifetime from Scoped to Transient. Including the db context.

services.AddDbContext<AppDbContext>(..options.., ServiceLifetime.Transient);

services.AddTransient<IService, Service>();
services.AddTransient<SomethingRepository>();

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 David Browne - Microsoft
Solution 3 Mayur
Solution 4 Roy Berris