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é
相关产品推荐
相关产品推荐

