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

首次访问上下文触发Cannot access a disposed context instance异常求助

解决Blazor Server首次访问DbContext触发Cannot access a disposed context instance异常

问题背景

在Blazor Server项目中,首次访问数据库上下文(DbContext)就触发Cannot access a disposed context instance异常,所有服务(包括DbContext)均注册为Transient生命周期,异常在调用QRCodeService.GetQRCodeAsync时抛出,该方法内部调用仓储的异步查询方法。

关键代码片段

DbContext注册代码

public static IServiceCollection AddSQLContext<TContext>(this IServiceCollection services, string connectionString, ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
            where TContext : DbContext => services
            .AddDbContext<TContext>(options => options.UseSqlServer(
               connectionString,
               actions => actions.MigrationsAssembly("Discounted.Data")
                                 .EnableRetryOnFailure()
               ), serviceLifetime);

Blazor组件调用代码

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        using (var source = new CancellationTokenSource())
        {
            version = VersionService.Version;
            ProtectedBrowserStorageResult<string> customer = await ProtectedSessionStore.GetAsync<string>("customer");
            DashboardDto.LocalUser = customer.Value;

            if (DashboardDto.LocalUser == null) return;

            // 获取订阅状态(不访问上下文)
            DashboardDto.LocalStatus = await SubscriptionService.GetSubscriptionStatusAsync(DashboardDto.LocalUser, source.Token);

            // 首次访问上下文,此处抛出异常
            DashboardDto.StoreId = await QRCodeService.GetQRCodeAsync(DashboardDto.LocalUser, source.Token);

            DashboardDto.Rewards = await RewardService.GetRewardAsync(DashboardDto.StoreId, source.Token);
        }

        StateHasChanged();
    }
}

QRCodeService及仓储调用代码

public class QRCodeService : IQRCodeService
{
    private IAccountRepository _accountRepository;

    public QRCodeService(IAccountRepository accountRepository)
    {
        _accountRepository = accountRepository;
    }

    public async Task<string?> GetQRCodeAsync(string customerId, CancellationToken cancellationToken)
    {
        // 在此处抛出异常
        Account store = await _accountRepository.FirstOrDefaultAsync(x => x.StripeId == customerId, cancellationToken);

        return store.StoreCode.ToString();
    }
}

可能的原因及解决方案

1. Blazor组件生命周期与Transient服务的冲突

Blazor Server的组件是长生命周期对象,而Transient服务每次从DI容器获取时都会创建新实例。但如果组件直接注入Transient服务,服务内部的DbContext可能会因为电路(Circuit)的范围变更被提前释放,即使首次调用也可能出现问题。

解决方案:在组件中使用IServiceScopeFactory创建临时服务范围,确保DbContext和相关服务在操作期间处于活跃状态:

// 先在组件构造函数注入IServiceScopeFactory
private readonly IServiceScopeFactory _serviceScopeFactory;

public YourComponent(IServiceScopeFactory serviceScopeFactory, ...)
{
    _serviceScopeFactory = serviceScopeFactory;
    // 其他注入项
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        using var scope = _serviceScopeFactory.CreateScope();
        var qrCodeService = scope.ServiceProvider.GetRequiredService<IQRCodeService>();
        var subscriptionService = scope.ServiceProvider.GetRequiredService<ISubscriptionService>();
        var rewardService = scope.ServiceProvider.GetRequiredService<IRewardService>();

        using (var source = new CancellationTokenSource())
        {
            version = VersionService.Version;
            ProtectedBrowserStorageResult<string> customer = await ProtectedSessionStore.GetAsync<string>("customer");
            DashboardDto.LocalUser = customer.Value;

            if (DashboardDto.LocalUser == null) return;

            DashboardDto.LocalStatus = await subscriptionService.GetSubscriptionStatusAsync(DashboardDto.LocalUser, source.Token);
            DashboardDto.StoreId = await qrCodeService.GetQRCodeAsync(DashboardDto.LocalUser, source.Token);
            DashboardDto.Rewards = await rewardService.GetRewardAsync(DashboardDto.StoreId, source.Token);
        }

        StateHasChanged();
    }
}

2. DbContext注册存在冲突

检查项目中是否有其他地方重复注册了同一个DbContext类型,比如同时使用AddDbContext和AddSQLContext注册同一个TContext,导致生命周期设置被覆盖,出现上下文提前释放的情况。

解决方案:确保DbContext只通过AddSQLContext注册一次,或者统一生命周期配置。

3. 改用Scoped生命周期的DbContext

Blazor Server中,DbContext的推荐生命周期是Scoped(对应电路范围),Transient生命周期在Blazor的长连接场景下容易出现释放问题。

解决方案:修改DbContext的注册为Scoped:

// 调用AddSQLContext时指定ServiceLifetime.Scoped
services.AddSQLContext<YourDbContext>(connectionString, ServiceLifetime.Scoped);

同时确保仓储和服务的生命周期与DbContext匹配(比如仓储也注册为Scoped),避免上下文被提前释放。

4. 排查同步阻塞异步代码的情况

检查项目中是否存在同步调用异步方法的场景(比如使用.Result、.Wait()),这种情况会导致线程阻塞,进而触发DbContext被提前释放。

解决方案:确保所有异步方法都通过await调用,避免同步阻塞。

5. 启用DbContext日志排查生命周期

通过启用DbContext的日志,查看上下文的创建和释放时间,确认是否在查询操作前就被释放:

public static IServiceCollection AddSQLContext<TContext>(this IServiceCollection services, string connectionString, ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
            where TContext : DbContext => services
            .AddDbContext<TContext>(options => options.UseSqlServer(
               connectionString,
               actions => actions.MigrationsAssembly("Discounted.Data")
                                 .EnableRetryOnFailure()
               )
               .LogTo(Console.WriteLine, LogLevel.Information), // 添加日志
               serviceLifetime);

查看日志中DbContext的Created和Disposed事件,判断是否在FirstOrDefaultAsync调用前就已经被释放。

内容的提问来源于stack exchange,提问作者Cornelis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 12:22:01