Blazor Web应用中仅Players页面导航后DbContext被释放,触发「无法访问已释放对象」错误
这种只在单个页面蹦出来的问题真的挺挠头的,毕竟其他页面都是抄的同样的CRUD逻辑,偏偏就Players页面出状况。我帮你梳理几个最可能的原因和解决方向,你可以挨个排查:
先查PlayersService的生命周期注册
你先去Program.cs里看看PlayersService是怎么注册的,要是注册成了AddSingleton<PlayersService>()就麻烦了——DbContext默认是Scoped生命周期,Singleton服务会长期持有Scoped的DbContext,等作用域结束(比如导航到新页面)DbContext被释放,Singleton还在引用它,自然就会报错。改成AddScoped<PlayersService>(),让它和DbContext的生命周期保持一致,才是正确的做法。检查PlayersService里DbContext的使用方式
要是PlayersService里把DbContext作为类的私有字段存起来,比如构造函数注入后就一直持有,然后在异步方法里延迟使用,那导航后组件所在的Scoped作用域被释放,DbContext也跟着被回收,这时候服务里的异步操作再去访问DbContext就会炸。
更稳妥的做法是用IDbContextFactory来创建DbContext实例,每次用的时候创建、用完就释放,比如:public class PlayersService { private readonly IDbContextFactory<YourDbContext> _contextFactory; public PlayersService(IDbContextFactory<YourDbContext> contextFactory) { _contextFactory = contextFactory; } public async Task<Player> GetPlayerByIdAsync(int id) { using var context = _contextFactory.CreateDbContext(); return await context.Players.FindAsync(id); } }这样每次方法调用都用独立的DbContext实例,完全不依赖外部作用域,就不会有被提前释放的问题。另外注意别返回延迟执行的IQueryable,要立刻用
ToListAsync()把结果拉到内存里,避免后续渲染时DbContext已经没了。排查页面组件里的未完成异步操作或事件订阅
看看Players页面里有没有在生命周期方法(比如OnInitializedAsync、OnAfterRenderAsync)里启动了未完成的异步任务,或者订阅了PlayersService的事件但没在组件销毁时取消订阅。比如如果组件里写了service.PlayerUpdated += HandlePlayerUpdated;,但没在DisposeAsync里取消,组件导航销毁后,服务还持有组件引用,可能会导致作用域混乱,连带DbContext被提前释放。检查Edit按钮的点击逻辑
会不会是点击Edit按钮时,你先触发了某个异步操作,还没等它完成就立刻导航了?比如类似这样的代码:<button @onclick="async () => { await service.SomeAsyncOperation(player.Id); navigationManager.NavigateTo($"/player/{player.Id}"); }">编辑</button>要是
SomeAsyncOperation还在访问DbContext的时候,导航已经触发导致组件和DbContext被释放,那异步操作后续的步骤就会报错。这种情况要确保异步操作完全完成后再导航,或者把操作逻辑移到Edit页面里处理。
另外因为其他页面都是复制粘贴的,你可以对比PlayersService和其他正常服务(比如SeasonsService)的代码实现、注册方式,大概率能快速找到差异点定位问题。
备注:内容来源于stack exchange,提问作者Rafael Osuna Dominguez

