Blazor Server中NavMenu.razor组件正确使用DbContext的方法及异常解决
你的问题核心是作用域服务的生命周期与事件触发时机不匹配,具体有两个关键点:
组件注入的
IServiceProvider已被释放
组件注入的IServiceProvider属于组件所在的Blazor渲染上下文(电路/作用域),当组件所在的作用域因超时、用户断开连接或组件卸载而被销毁时,这个IServiceProvider会被释放。但你的LuahAuthService是单例服务,它的OnChange事件订阅会一直持有组件的引用,当后续事件触发时,调用已释放的ServiceProvider就会抛出ObjectDisposedException。直接注入的
ScheduleOps作用域过期ScheduleOps是Scoped服务,它的生命周期和组件所在的作用域绑定。当作用域过期(比如Blazor Server默认的作用域超时),或者事件触发时原作用域已被销毁,依赖的LuahDbContext也会被释放,从而出现“过期的DbContext”错误。
1. 使用IServiceScopeFactory创建独立作用域
替换组件中的IServiceProvider注入为IServiceScopeFactory,它是Singleton服务,可以安全地创建新的独立作用域,避免依赖组件所在的已释放作用域:
首先修改组件注入:
@inject IServiceScopeFactory ScopeFactory @inject ScheduleOps ScheduleOps @inject MenuService MenuService @inject LuahAuthService AuthService @implements IDisposable
然后修改HandleMenuChangeAsync方法:
private async Task HandleMenuChangeAsync() { var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); if (!authState.User.Identity.IsAuthenticated) return; var userid = authState.User.FindFirst("userid").Value; List<Schedule> lst = null; try { using (var scope = ScopeFactory.CreateScope()) { var scheduleOps = scope.ServiceProvider.GetRequiredService<ScheduleOps>(); lst = await scheduleOps.CustomerSchedules(userid); // 这里可使用lst更新菜单数据 } } catch (Exception ex) { Console.WriteLine(ex.Message); } }
2. 取消事件订阅避免内存泄漏
因为LuahAuthService是单例,组件卸载时必须取消订阅OnChange事件,否则单例会持有组件引用导致内存泄漏,同时也会继续触发已销毁组件的事件处理方法:
public void Dispose() { AuthService.OnChange -= HandleMenuChangeAsync; }
3. 优化事件订阅逻辑
确保只订阅一次事件,避免重复绑定:
private bool _hasSubscribed; protected override Task OnAfterRenderAsync(bool firstRender) { if (!_hasSubscribed) { AuthService.OnChange += HandleMenuChangeAsync; _hasSubscribed = true; } return base.OnAfterRenderAsync(firstRender); }
IServiceScopeFactory是Singleton服务,生命周期与应用一致,创建的新作用域完全独立于组件原有作用域,作用域内的Scoped服务(ScheduleOps、LuahDbContext)会在作用域释放时被正确回收,不会受组件生命周期影响。- 取消事件订阅可以切断单例服务与已销毁组件的引用关联,避免内存泄漏和无效的事件调用。
内容的提问来源于stack exchange,提问作者Michael EstrinOne

