Blazor Server 和 EF Core:在上一个操作完成之前,在此上下文实例上启动了第二个操作
Blazor Server and EF Core: A second operation was started on this context instance before a previous operation completed
我的 ef 核心有问题。我有两个从数据库读取数据的服务。在一页上称为第一个服务,在第二页上称为第二个服务。当我点击按钮创建一个新程序时,我得到了错误。我通常从带有注入服务的页面调用它。有人可以帮我吗?
Show in application
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 Server 中,DI 范围是用户电路 - 本质上是用户会话。这意味着像 TestService2
或 DbContext
这样的 scoped
服务将在内存中保留很长时间,并最终被多种方法和操作重用。
正如文档所解释的:
Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a circuit. One example of user state is data held in dependency injection (DI) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use 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())
{
...
}
}
组件范围
要将 DbContext 的范围限制为单个组件,仅注入 DbContextFactory 是不够的。当用户导航离开组件时,需要显式释放 DbContext 实例。为此,组件需要实现 IDisposable。 Scope to the component lifetime
部分对此进行了解释
@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
...
@code
{
ContactContext? Context;
public void Dispose()
{
Context?.Dispose();
}
protected override async Task OnInitializedAsync()
{
Context = DbFactory.CreateDbContext();
...
}
}
我的 ef 核心有问题。我有两个从数据库读取数据的服务。在一页上称为第一个服务,在第二页上称为第二个服务。当我点击按钮创建一个新程序时,我得到了错误。我通常从带有注入服务的页面调用它。有人可以帮我吗?
Show in application
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 Server 中,DI 范围是用户电路 - 本质上是用户会话。这意味着像 TestService2
或 DbContext
这样的 scoped
服务将在内存中保留很长时间,并最终被多种方法和操作重用。
正如文档所解释的:
Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a circuit. One example of user state is data held in dependency injection (DI) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use 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())
{
...
}
}
组件范围
要将 DbContext 的范围限制为单个组件,仅注入 DbContextFactory 是不够的。当用户导航离开组件时,需要显式释放 DbContext 实例。为此,组件需要实现 IDisposable。 Scope to the component lifetime
部分对此进行了解释@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
...
@code
{
ContactContext? Context;
public void Dispose()
{
Context?.Dispose();
}
protected override async Task OnInitializedAsync()
{
Context = DbFactory.CreateDbContext();
...
}
}