繁体   English   中英

Blazor 服务器和 EF Core:在上一个操作完成之前在此上下文实例上启动了第二个操作

[英]Blazor Server and EF Core: A second operation was started on this context instance before a previous operation completed

我对 ef 核心有疑问。 我有两个从数据库读取数据的服务。 在一页上称为第一个服务,在第二页上称为第二个服务。 当我点击按钮创建一个新程序时,我得到了错误。 我通常从带有注入服务的页面调用它。 有人可以帮我吗?

在应用程序中显示

builder.Services.AddDbContextPool<Context>(options =>
{ 
options.UseSqlServer(builder.Configuration.GetConnectionString("Connection"));
});

测试服务 1:

public class TestService1 : ITestService1
{
    private readonly Context _context;
    private readonly IMapper _mapper;

    public TestService1(Context context, IMapper mapper)
    {
        _kreativgangContext = kreativgangContext;
        _mapper = mapper;
    }

    public virtual async Task<AllProgramViewModel> HandleAsync(AllProgramFilterViewModel filter)
    {
        var model = new AllProgramViewModel();

        var data = _context.Programs.Where(x => (EF.Functions.Like(x.Name ?? "", "%" + filter.Name + "%") || string.IsNullOrEmpty(filter.Name)))
            .Select(x => new Core.Models.Program() { ID = x.ID, Name = x.Name, Order = x.Order });

        result.Model.TotalCount = await data.CountAsync();

        result.Model.Items = data.Select(x => _mapper.Map<AllProgramItemViewModel>(x));
    
        return model;
    }
}

public interface ITestService1
{
    public Task<AllProgramViewModel> HandleAsync(AllProgramFilterViewModel filter);
}

测试服务2:

    public class TestService2 : ITestService2
{
    private readonly Context _context;

    public TestService2(Context context)
    {
        _context = context;
    }

    public virtual async Task<NewProgramViewModel> HandleAsync()
    {
        var model = new NewProgramViewModel();

        List<ProgramOrderViewModel> items = _context.Programs.Select(x => new Core.Models.Program() { Order = x.Order, ID = x.ID })
            .Select(x => new ProgramOrderViewModel()
            {
                ID = x.ID,
                Order = x.Order
            }).ToList();

        return await Task.FromResult(model);
    }
}

public interface ITestService2
{
    public Task<NewProgramViewModel> HandleAsync();
}

错误:

Error: System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently 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.
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Mitar.Kreativgang.Admin.Handlers.TestService2.HandleAsync() in D:\Programming\Kreativgang\Src\Mitar.Kreativgang.Admin\Handlers\TestService2.cs:line 26
   at Mitar.Kreativgang.Admin.Pages.Program.ProgramNew.OnInitializedAsync() in D:\Programming\Kreativgang\Src\Mitar.Kreativgang.Admin\Pages\Program\ProgramNew.razor:line 114
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()

这是一个已知的记录在案的陷阱,在ASP.NET Core Blazor Server with Entity Framework Core (EFCore)中进行了解释。 在 Blazor 服务器中,DI scope 是用户电路 - 本质上是用户 session。这意味着像TestService2DbContext这样的scoped服务将长期保留在 memory 中,并最终被多种方法和操作重用。

正如文档所解释的那样:

Blazor 服务器是一个有状态的应用程序框架。 该应用程序保持与服务器的持续连接,并且用户的 state 被保存在服务器的 memory 中。 用户 state 的一个示例是依赖注入 (DI) 服务实例中保存的数据,这些实例的范围是电路。 Blazor 服务器提供的独特应用程序 model 需要一种特殊的方法来使用 Entity Framework Core。

您需要注册并使用 DbContextFactory(或PooledDbContextFactory )而不是 DbContextPool,并在使用它的地方创建一个新的 DbContext 实例。

builder.Services.AddDbContextFactory<ContactContext>(opt =>
    opt.UseSqlServer(...));

或者

builder.Services.AddPooledDbContextFactory<ContactContext>(opt =>
    opt.UseSqlServer(...));

服务构造函数应该接受工厂而不是上下文:

    public TestService2(AddDbContextFactory<ContactContext> factory)
    {
        _factory = factory;
    }

    public virtual async Task<NewProgramViewModel> HandleAsync()
    {
        
        using var context=_factory.CreateContext())
        {
        ...
        }

    }

组件 Scope

要将 DbContext 的 scope 限制为单个组件,仅注入 DbContextFactory 是不够的。 当用户导航离开组件时,需要显式释放 DbContext 实例。 为此,组件需要实现 IDisposable。 在组件寿命的 Scope部分中进行了解释

@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
...

@code 
{

    ContactContext? Context;

    public void Dispose()
    {
        Context?.Dispose();
    }

    protected override async Task OnInitializedAsync()
    {
        Context = DbFactory.CreateDbContext();
        ...
    }

}
 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM