Blazor: A second operation started on this context

2020-02-13 06:52发布

问题:

I'm creating a server side Blazor app. The following code is in the Startup.cs.

services.AddDbContext<MyContext>(o => o.UseSqlServer(Configuration.GetConnectionString("MyContext")), ServiceLifetime.Transient);
services.AddTransient<MyViewModel, MyViewModel>();

And in the ViewModel:

public class MyViewModel : INotifyPropertyChanged
{
    public MyViewModel(MyContext referenceContext)
    {
        _myContext = myContext;
    }

    public async Task<IEnumerable<Dto>> GetList(string s)
    {
        return await _myContext.Table1.where(....)....ToListAsync();
    }

And in the razor file.

@inject ViewModels.MyViewModel VM
<input id="search" type="text" @bind="search" />
<input id="search" type="button" value="Go" @onclick="SearchChanged" />   
@code {
    string search = "";
    int currentCount = 0;
    async void SearchChanged() {
        currentCount++;
        dtos = GetList(search);
    }
}

However, sometimes the following error occur when clicking the search button?

System.InvalidOperationException: 'A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.'

回答1:

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

Here Daniel Roth (Blazor Product Manager) talking about Using Entity Framework Core with Blazor



回答2:

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
    }
}