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

Entity Framework偶发DataReader已关联Command需先关闭异常如何解决

问题根因

  • ASP.NET Core 中 IAuthorizationHandler 的默认注册生命周期是单例,你代码中直接将作用域(Scoped)生命周期的 IUserRepository 注入到单例服务中,会导致被注入的 IUserRepository 及其依赖的 UnitOfWork、DbContext 都被提升为单例。
  • EF 6 的 DbContext 本身不是线程安全的,单例的 DbContext 会被所有请求共享,多请求并发调用查询方法时,就会出现同一个连接上同时打开多个DataReader的异常。
  • 本地单测无法复现是因为测试时是单独调用Repository,没有触发单例Handler下多请求共享DbContext的并发场景。

排查验证

你可以检查服务注册代码中各依赖的生命周期配置:
如果 DbContext 是注册为 Scoped,而 UserAuthorizationHandler 是默认的 Singleton 生命周期,即可确认是生命周期不匹配导致的问题。

修复方案

方案1:授权Handler中手动获取作用域服务(最优,无需修改现有业务逻辑)

不要直接在构造函数注入IUserRepository,改为注入IServiceProvider,每次执行授权逻辑时手动创建作用域获取IUserRepository实例,保证每个请求的授权逻辑都使用独立的DbContext:

public class UserAuthorizationHandler : IAuthorizationHandler
{
    private readonly IServiceProvider _serviceProvider;
    public UserAuthorizationHandler(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        // 每次处理授权时创建独立作用域
        using var scope = _serviceProvider.CreateScope();
        var userRepository = scope.ServiceProvider.GetRequiredService<IUserRepository>();
        var userId = // 保留原有从context.User中解析用户ID的逻辑
        
        var pendingRequirements = context.PendingRequirements.ToList();
        foreach (var requirement in pendingRequirements)
        {
            switch (requirement)
            {
                case PremiumRequirement:
                    if (userRepository.IsPremium(userId))
                    {
                        context.Succeed(requirement);
                    }
                    break;
                case FeatureActivatedRequirement:
                    if (userRepository.IsFeatureActivated(userId))
                    {
                        context.Succeed(requirement);
                    }
                    break;
            }
        }
        return Task.CompletedTask;
    }
}

方案2:合并Repository查询方法,减少单次授权的查询次数

将两个校验逻辑合并为单次查询,避免同一个授权流程中两次调用DbContext,降低并发冲突概率:

// 在IUserRepository中新增方法
public (bool IsPremium, bool IsFeatureActivated) GetUserAuthorizationStatus(int userId)
{
    var user = DbSet.AsNoTracking()
        .Where(u => u.Id == userId)
        .Select(u => new { u.IsPremium, u.IsFeatureActivated })
        .FirstOrDefault();
    return (user?.IsPremium ?? false, user?.IsFeatureActivated ?? false);
}

授权逻辑中只调用一次该方法获取两个状态即可。

方案3:将授权Handler注册为Scoped生命周期

修改服务注册代码,显式将UserAuthorizationHandler注册为Scoped,使其生命周期和DbContext匹配:

services.AddScoped<IAuthorizationHandler, UserAuthorizationHandler>();

注意该方案需要确认你的授权场景没有在单例服务中调用该Handler的逻辑,否则还是会出现生命周期不匹配的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 09:45:02