Blazor: A second operation started on this context before a previous operation completed
Edited Aug 2020
Official guidance: https://docs.microsoft.com/ca-es/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-3.1 with several solutions. In my opinion, the best approach on post is "Create new DbContext instances":
The recommended solution to create a new DbContext with dependencies is to use a factory.
//The factory
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace BlazorServerDbContextExample.Data
{
public class DbContextFactory<TContext>
: IDbContextFactory<TContext> where TContext : DbContext
{
private readonly IServiceProvider provider;
public DbContextFactory(IServiceProvider provider)
{
this.provider = provider;
}
public TContext CreateDbContext()
{
if (provider == null)
{
throw new InvalidOperationException(
$"You must configure an instance of IServiceProvider");
}
return ActivatorUtilities.CreateInstance<TContext>(provider);
}
}
}
Injecting the factory:
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
Using the factory:
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;
var contact = await context.Contacts.FirstAsync(
c => c.Id == Wrapper.DeleteRequestId);
if (contact != null)
{
context.Contacts.Remove(contact);
await context.SaveChangesAsync();
}
Filters.Loading = false;
await ReloadAsync();
}
My deprecated answer:
You can try to create a new scope for each request:
public class MyViewModel : INotifyPropertyChanged
{
protected readonly IServiceScopeFactory _ServiceScopeFactory;
public MyViewModel(IServiceScopeFactory serviceScopeFactory)
{
_ServiceScopeFactory = serviceScopeFactory;
}
public async Task<IEnumerable<Dto>> GetList(string s)
{
using (var scope = _ServiceScopeFactory.CreateScope())
{
var referenceContext = scope.ServiceProvider.GetService<MyContext>();
return await _myContext.Table1.where(....)....ToListAsync();
}
}
In the following screenshot you can see a sample case of this issue. User clicks quickly in several pagination elements. A new request starts before previous one is ended.
Here Daniel Roth (Blazor Product Manager) talking about Using Entity Framework Core with Blazor
The error message is to do with the fact the EF context can't perform more than one operation at a time.
My understanding is that if you're on a page, then you have a constant connection through to the "Service" file via a SingalR connection.
If your page makes multiple calls through to the Service, then it could be that the Context is being called to perform an operation before it has completed the previous one.
Rather than have one instance of the Context for the lifetime of the Service, I've create one instance per call. It seems to mitigate this problem, but whether it's seen as "best practice" I'm not yet sure.
So, for example:
public class MyService
{
private MyContext Context => new MyContext(new DbContextOptions<MyContext>()));
private async Task DoSomething()
{
await using var context = this.Context; //New context for the lifetime of this method
var r = await context.Something
.Where(d => d....)
.AsNoTracking()
.FirstOrDefaultAsync()
.ConfigureAwait(false);
// context gets disposed of
// Other code
}
private async Task DoSomethingElse()
{
await using var context = this.Context; //New context for the lifetime of this method
var r = await context.Something
.Where(d => d....)
.AsNoTracking()
.FirstOrDefaultAsync()
.ConfigureAwait(false);
// context gets disposed of
// Other code
}
}
In my case, I solved turned AddDbContext ServiceLifetime.Transient
services.AddDbContext<MY_Context>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Transient);