You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 03:25:08