Blazor:在上一个操作完成之前,在此上下文中启动了第二个操作

ca9*_*3d9 9 c# entity-framework entity-framework-core blazor blazor-server-side

我正在创建一个服务器端 Blazor 应用程序。以下代码位于Startup.cs.

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

在 ViewModel 中:

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

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

并在剃刀文件中。

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,有时单击搜索按钮时会出现以下错误?

System.InvalidOperationException: '在上一个操作完成之前,在此上下文中启动了第二个操作。这通常是由使用相同 DbContext 实例的不同线程引起的。有关如何避免 DbContext 线程问题的详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2097913

dan*_*era 13

2020 年 8 月编辑

官方指南:https : //docs.microsoft.com/ca-es/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-3.1提供多种解决方案。在我看来,帖子的最佳方法是“创建新的 DbContext 实例”:

创建具有依赖项的新 DbContext 的推荐解决方案是使用工厂。

//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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注入工厂:

services.AddDbContextFactory<ContactContext>(opt =>
    opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
    .EnableSensitiveDataLogging());
Run Code Online (Sandbox Code Playgroud)

使用工厂:

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();
}
Run Code Online (Sandbox Code Playgroud)

我弃用的答案:

您可以尝试为每个请求创建一个新范围:

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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

在以下屏幕截图中,您可以看到此问题的示例案例。用户在几个分页元素中快速点击。新请求在前一个请求结束之前开始。

屏幕截图:它显示了新请求如何在前一个请求结束之前开始

这里 Daniel Roth(Blazor 产品经理)谈论将 Entity Framework Core 与 Blazor 一起使用

  • @daniherrera 你还在使用这个解决方案来解决你上面链接到的 github 问题吗?我遇到了同样的问题,有人可以向按钮发送垃圾邮件并导致应用程序崩溃...... (2认同)