EF Core偶发IServiceProvider ObjectDisposedException问题求助
偶发ObjectDisposedException(IServiceProvider已释放)问题解决
问题根源
这个偶发异常的核心原因是DbContextFactory与依赖服务的生命周期不匹配:你注册了Transient生命周期的DbContextFactory,但业务服务LoadPurchaseService是Scoped(Blazor Server中对应Circuit生命周期)。当Circuit的Scoped服务容器被回收时,若仍有异步操作在使用由Transient Factory创建的DbContext,EF Core内部依赖的IServiceProvider可能已被释放,从而抛出异常。
修复方案
方案1:对齐DbContextFactory的生命周期为Scoped
修改Program.cs中AddDbContextFactory的生命周期配置,与Blazor Server的Circuit生命周期保持一致:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContextFactory<MyDbContext>(options => options.UseSqlServer( builder.Configuration.GetConnectionString("db"), sqlServerOptions => sqlServerOptions.CommandTimeout(5) ), ServiceLifetime.Scoped); // 将Transient改为Scoped builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddScoped<LoadDataPort, LoadPurchaseService>(); [...]
Scoped的Factory会绑定到当前Circuit的服务容器,避免异步操作中访问已释放的服务提供者。
方案2:合并数据库查询减少异步嵌套
当前代码中两次独立的数据库查询可以合并为并行执行,减少异步操作的嵌套层级,同时避免跨DbContext的生命周期冲突:
public class LoadPurchaseService : LoadDataPort { public async Task<List<Purchase>> GetAllPurchases() { using MyDbContext dbContext = contextFactory.CreateDbContext(); // 并行发起两个查询请求 var purchasesTask = dbContext.Purchase .Include(purchase => purchase.Status) .AsNoTracking() .OrderByDescending(n => n.SomeId) .AsSplitQuery() .TagWithCallSite() .TagWith("GetAllPurchases") .ToListAsync(); var categoriesTask = dbContext.PurchaseCategory.AsNoTracking() .OrderBy(category => category.Id) .ToListAsync(); await Task.WhenAll(purchasesTask, categoriesTask); var purchases = purchasesTask.Result; var allCategories = categoriesTask.Result; // 关联采购与分类数据 purchases.ForEach(purchase => { purchase.Categories = purchase.CategoriesIds .Join(allCategories, catId => catId, cat => cat.Id, (_, cat) => cat) .ToList(); }); return purchases; } }
这种方式不仅提升了查询性能,还消除了跨方法异步调用带来的生命周期风险。
方案3:同步服务与Factory的生命周期
如果需要保留Transient的DbContextFactory,可以将LoadPurchaseService也注册为Transient,确保每次服务调用都使用全新的实例:
builder.Services.AddTransient<LoadDataPort, LoadPurchaseService>();
避免Scoped服务长期持有Transient Factory实例,导致后续异步操作中访问已释放的依赖服务。
额外建议
- Blazor Server中,Scoped服务绑定到Circuit生命周期,Transient服务则是每次请求创建,尽量避免在Scoped服务中持有Transient服务实例,尤其是涉及异步操作的场景。
- 对于分类这类变更频率低、访问频率高的数据,可以通过
IMemoryCache或IDistributedCache做内存缓存,减少数据库查询次数,同时从根源上避免此类生命周期问题。
内容的提问来源于stack exchange,提问作者Wolfware
相关产品推荐
相关产品推荐

