Blazor Server中EF Core出现Cannot access a disposed context实例问题求助
解决Blazor Server中仓储层返回IQueryable导致上下文已释放的问题
问题根源
IQueryable是延迟执行的:仓储层GetTableAsync方法的using块结束时,DbContext会被自动释放,但服务层后续调用ToListAsync才真正触发查询执行,此时上下文已不存在,因此抛出Cannot access a disposed context instance异常。
解决方案
方案1:让仓储层负责执行查询(推荐,符合仓储模式职责)
调整仓储接口,将查询执行逻辑放在仓储内部,确保上下文在查询完成前始终存活。
修改仓储接口:
public interface IRepository<T> where T : BaseEntity { // 直接返回查询结果,而非IQueryable Task<List<T>> GetListAsync(Expression<Func<T, bool>>? filter = null, CancellationToken cancellationToken = default); // 保留其他原有方法 }
仓储实现:
public sealed class EFRepository<T, TDbContext> : IRepository<T> where T : BaseEntity where TDbContext : DbContext { private readonly IDbContextFactory<TDbContext> _contextFactory; public EFRepository(IDbContextFactory<TDbContext> contextFactory) { _contextFactory = contextFactory; } public async Task<List<T>> GetListAsync(Expression<Func<T, bool>>? filter = null, CancellationToken cancellationToken = default) { using var context = await _contextFactory.CreateDbContextAsync(cancellationToken); var query = context.Set<T>().AsQueryable(); // 支持传入过滤条件 if (filter != null) { query = query.Where(filter); } // 仓储内完成查询执行,上下文存活到查询结束 return await query.ToListAsync(cancellationToken); } }
服务层调用:
public async Task<List<Dealer>?> GetDealersAsync(CancellationToken cancellationToken = default) { // 直接获取结果,无需处理IQueryable return await _dealerRepository.GetListAsync(cancellationToken: cancellationToken); }
方案2:使用查询执行器模式(保留IQueryable灵活性)
如果需要在服务层灵活组合IQueryable(如过滤、排序、分页),可以让仓储提供一个接受查询委托的方法,在上下文存活的范围内执行查询。
修改仓储接口:
public interface IRepository<T> where T : BaseEntity { // 接受查询逻辑委托,在上下文存活时执行 Task<TResult> ExecuteQueryAsync<TResult>(Func<IQueryable<T>, Task<TResult>> queryFunc, CancellationToken cancellationToken = default); }
仓储实现:
public sealed class EFRepository<T, TDbContext> : IRepository<T> where T : BaseEntity where TDbContext : DbContext { private readonly IDbContextFactory<TDbContext> _contextFactory; public EFRepository(IDbContextFactory<TDbContext> contextFactory) { _contextFactory = contextFactory; } public async Task<TResult> ExecuteQueryAsync<TResult>(Func<IQueryable<T>, Task<TResult>> queryFunc, CancellationToken cancellationToken = default) { using var context = await _contextFactory.CreateDbContextAsync(cancellationToken); var table = context.Set<T>(); // 在上下文存活的using块内执行传入的查询逻辑 return await queryFunc(table); } }
服务层复杂查询示例:
public async Task<List<Dealer>?> GetActiveDealersAsync(int page, int pageSize, CancellationToken cancellationToken = default) { return await _dealerRepository.ExecuteQueryAsync( query => query.Where(d => d.IsActive) .OrderBy(d => d.Name) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(cancellationToken), cancellationToken ); }
方案3:返回上下文+IQueryable包装类(不推荐,需手动管理资源)
如果一定要返回IQueryable,可以创建包装类持有上下文和查询,服务层需手动释放上下文(容易遗漏导致资源泄漏)。
创建包装类:
public sealed class QueryableContext<T> : IDisposable { public IQueryable<T> Query { get; } private readonly DbContext _context; public QueryableContext(IQueryable<T> query, DbContext context) { Query = query; _context = context; } public void Dispose() { _context.Dispose(); } }
修改仓储接口与实现:
public interface IRepository<T> where T : BaseEntity { Task<QueryableContext<T>> GetTableAsync(CancellationToken cancellationToken = default); } public async Task<QueryableContext<T>> GetTableAsync(CancellationToken cancellationToken = default) { var context = await _contextFactory.CreateDbContextAsync(cancellationToken); var table = context.Set<T>(); return new QueryableContext<T>(table, context); }
服务层调用:
public async Task<List<Dealer>?> GetDealersAsync(CancellationToken cancellationToken = default) { // 必须用using释放包装类,否则上下文不会被释放 using var queryContext = await _dealerRepository.GetTableAsync(cancellationToken); return await queryContext.Query.ToListAsync(cancellationToken); }
总结
优先选择方案1(查询逻辑固定时)或方案2(需要灵活组合查询时),这两种方案都能确保上下文生命周期与查询执行周期一致,避免资源泄漏和异常。方案3仅作为特殊场景下的备选,需谨慎使用。
内容的提问来源于stack exchange,提问作者DM-98
相关产品推荐
相关产品推荐

