Blazor中快速页面导航为何在每页初始化Entity Framework Core时引发问题?
问题原因
当在Blazor Server中快速切换页面时,已加载页面的组件会被销毁,但之前启动的Entity Framework Core异步查询可能还在后台执行。此时:
- 组件关联的Scoped DbContext可能已被回收,数据库连接被关闭
- 组件销毁后,后续的查询操作尝试读取已关闭的数据库读取器,就会抛出
Invalid attempt to call ReadAsync when reader is closed异常
解决方案
1. 组件销毁时取消异步查询
在页面组件中使用CancellationTokenSource,在组件销毁时取消未完成的EF Core查询:
组件代码示例
@implements IDisposable @inject PacientesServices _pacientesServices @* 页面内容 *@ @code { private List<Paciente> pacientes = new(); private CancellationTokenSource _cts = new(); protected override async Task OnInitializedAsync() { try { pacientes = await _pacientesServices.GetAllPacientes(_cts.Token); } catch (OperationCanceledException) { // 导航导致的查询取消,无需处理 } } public void Dispose() { _cts.Cancel(); _cts.Dispose(); } }
服务层代码修改
更新服务方法,接受并传递CancellationToken给EF Core的异步操作:
public async Task<List<Paciente>> GetAllPacientes(CancellationToken cancellationToken = default) { // 将令牌传递给ToListAsync,确保查询能被取消 return await _context.Pacientes.ToListAsync(cancellationToken); }
2. 确保DbContext生命周期配置正确
在Blazor Server中,DbContext应配置为Scoped生命周期(默认配置),这样每个电路(用户会话)对应一个DbContext实例,与组件生命周期匹配。避免使用Singleton的DbContext,这会导致多线程冲突和资源泄漏。
配置示例(Program.cs):
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
3. 全局捕获取消类异常
在Blazor Server的全局异常处理中忽略OperationCanceledException,因为这是用户导航导致的正常取消,不属于错误:
在Program.cs中添加:
app.UseExceptionHandler("/Error"); // 自定义异常处理逻辑 app.Use(async (context, next) => { try { await next(); } catch (OperationCanceledException) { // 忽略导航取消导致的异常 } });
关键说明
- 传递
CancellationToken是核心:让EF Core查询能在组件销毁时及时终止,避免访问已关闭的资源 - 捕获
OperationCanceledException可以防止异常被记录到日志或显示给用户,提升体验
内容的提问来源于stack exchange,提问作者Rubén Díaz Dugo
相关产品推荐
相关产品推荐

