Blazor:在上一个操作完成之前,第二个操作在此上下文中开始
Blazor: A second operation started on this context before a previous operation completed
我正在创建服务器端 Blazor 应用。以下代码在Startup.cs
.
services.AddDbContext<MyContext>(o => o.UseSqlServer(Configuration.GetConnectionString("MyContext")), ServiceLifetime.Transient);
services.AddTransient<MyViewModel, MyViewModel>();
在 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();
}
并且在 razor 文件中。
@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);
}
}
但是,点击搜索按钮时有时会出现以下错误?
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.'
该错误消息与 EF 上下文一次不能执行多个操作这一事实有关。
我的理解是,如果您在一个页面上,那么您可以通过 SingalR 连接持续连接到 "Service" 文件。
如果您的页面多次调用该服务,则可能是上下文在完成前一个操作之前被调用以执行操作。
我没有在服务的生命周期内拥有一个上下文实例,而是为每次调用创建一个实例。它似乎减轻了这个问题,但它是否被视为 "best practice" 我还不确定。
因此,例如:
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
}
}
已编辑 2020 年 8 月
官方指导:https://docs.microsoft.com/ca-es/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-3.1 有几个解决方案。在我看来,post 上的最佳方法是“创建新的 DbContext 实例”:
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);
}
}
}
注入工厂:
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
使用工厂:
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();
}
我弃用的答案:
您可以尝试为每个请求创建一个新的范围:
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();
}
}
在下面的屏幕截图中,您可以看到此问题的示例。用户在几个分页元素中快速点击。一个新的请求在前一个请求结束之前开始。
Daniel Roth(Blazor 产品经理)在这里谈论 Using Entity Framework Core with Blazor
我解决了这个问题,但是我认为我丢失了工作单元,因为现在我有多个 dbContex :
构造函数:
private AppDbContext _db;
protected override void OnInitialized()
{
_db = new AppDbContext();
var query = _db.Set<Group>().AsQueryable();
}
后来我处理了它:
public void Dispose()
{
_db?.Dispose();
}
在我的例子中,我解决了转AddDbContext ServiceLifetime.Transient
services.AddDbContext<MY_Context>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Transient);
我正在创建服务器端 Blazor 应用。以下代码在Startup.cs
.
services.AddDbContext<MyContext>(o => o.UseSqlServer(Configuration.GetConnectionString("MyContext")), ServiceLifetime.Transient);
services.AddTransient<MyViewModel, MyViewModel>();
在 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();
}
并且在 razor 文件中。
@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);
}
}
但是,点击搜索按钮时有时会出现以下错误?
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.'
该错误消息与 EF 上下文一次不能执行多个操作这一事实有关。
我的理解是,如果您在一个页面上,那么您可以通过 SingalR 连接持续连接到 "Service" 文件。
如果您的页面多次调用该服务,则可能是上下文在完成前一个操作之前被调用以执行操作。
我没有在服务的生命周期内拥有一个上下文实例,而是为每次调用创建一个实例。它似乎减轻了这个问题,但它是否被视为 "best practice" 我还不确定。
因此,例如:
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
}
}
已编辑 2020 年 8 月
官方指导:https://docs.microsoft.com/ca-es/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-3.1 有几个解决方案。在我看来,post 上的最佳方法是“创建新的 DbContext 实例”:
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);
}
}
}
注入工厂:
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
使用工厂:
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();
}
我弃用的答案:
您可以尝试为每个请求创建一个新的范围:
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();
}
}
在下面的屏幕截图中,您可以看到此问题的示例。用户在几个分页元素中快速点击。一个新的请求在前一个请求结束之前开始。
Daniel Roth(Blazor 产品经理)在这里谈论 Using Entity Framework Core with Blazor
我解决了这个问题,但是我认为我丢失了工作单元,因为现在我有多个 dbContex :
构造函数:
private AppDbContext _db;
protected override void OnInitialized()
{
_db = new AppDbContext();
var query = _db.Set<Group>().AsQueryable();
}
后来我处理了它:
public void Dispose()
{
_db?.Dispose();
}
在我的例子中,我解决了转AddDbContext ServiceLifetime.Transient
services.AddDbContext<MY_Context>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Transient);