EF。连接未关闭。连接的当前状态为连接。

10
我有JWT认证:
var messageHandlers = new JwtMessageHandler(_serviceProvider);

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    Events = new JwtBearerEvents
    {
        OnMessageReceived = messageHandlers.OnMessageReceived,
    },
    TokenValidationParameters = tokenValidationParameters
});

JwtMessageHandler 是我的自定义处理程序。在这个处理程序中,我需要对数据库进行一些查询,因此我传递了 ServiceProvider 并解析了我的用户服务:

public class JwtMessageHandler
{

        private IUserService _userService;  

        public async Task OnMessageReceived(MessageReceivedContext arg)
        {
             //parsing header, get claims from token
             ...
              _userService = (IUserService)arg.HttpContext.RequestServices.GetService(typeof(IUserService));
             var isRoleChanged = await _userService.IsRoleChanged(tokenObject.Subject, rolesFromToken);

            if (isRoleChanged)
            {
                GenerateBadResponse(arg);
                return;
            }

            var canLogin = await _userService.CanLogin(tokenObject.Subject);

            if (!canLogin)
            {
                GenerateBadResponse(arg);
                return;
            }
        }    
}

在这个服务中,我进行查询:
...
 var user = await _userManager.FindByEmailAsync(email);
 var currentRoles = await _userManager.GetRolesAsync(user);
..
OnMessageReceived 方法会在每个请求上调用。当我只有一个页面向服务器发送请求,或者等待一两秒钟再进行操作时,一切工作正常。但是,在我有几个页面同时向服务器发出2-3个请求时,就会出现以下错误:

连接未关闭,连接的当前状态为连接中。

我理解这是多线程问题。 JwtMessageHandler 在应用程序启动时创建一次。因此,我添加了以下代码:

_userService = (IUserService)_serviceProvider.GetService(typeof(IUserService)); 

在方法中,我曾尝试过将某些代码移到构造函数中,但没有效果。同时,在方法结束后,我还尝试将_userService设置为null。

请问在这种情况下应该如何正确使用?


而且,如果您尝试使用在“当前”范围之外创建的serviceProvider来获取作用域服务,则非常危险。请查看MessageReceivedContext内部 - 那里应该有其他ServiceProvider,直接或通过HttpContext...从那里请求您的服务。 - Dmitry
每个对您的应用程序的请求都会创建单独的HttpRequest、单独的作用域和单独的IUserService实例,这些实例应该使用单独的DbContext实例。您的DbContext注册是否“通常”?您确定您在等待OnMessageReceived本身吗?也许在请求处理链中有些东西在您执行IsRoleChanged时查询了数据库? - Dmitry
@Dmitry。我稍微更新了问题中的代码。是的,DbContext注册通常是从文档中获取的,没有什么特别的。UserService内的所有方法都是等待的。 - user348173
您的_userService变量存在竞态条件。请删除private IUserService _userService,并在方法内部使用var _userService =... - Dmitry
我已经删除了它。稍微测试一下。 - user348173
显示剩余3条评论
4个回答

22

尝试使用已经处于“连接”状态的连接,这是一些竞争条件的明显迹象。

  1. 重新检查IUserService是否以“作用域”生存期注册,并且它的所有依赖项(userManager、dbContext)也是如此。
  2. 不要使用在应用程序启动期间获取的IServiceProvider进行范围为基础的服务解析-它与当前请求范围无关,并从“其他宇宙”返回实例。使用HttpContext.RequestServices进行服务解析。
  3. 检查您是否“等待”所有异步方法。如果您在执行第一个请求时开始第二个请求,则可能会在“连接”阶段捕获dbContext。
  4. 您的JwtMessageHandler实例是一个/单个应用程序。因此,不要使用其属性来存储_userService(删除private IUserService _userService)。而是在OnMessageReceived内使用局部变量(var _userService = ...)。

您已经检查了(1)、(2)和(3)。我认为(4)是您需要修复错误的最后一个问题。


我测试了2个小时,一切正常。非常感谢。 - user348173
1
@Dmitry,感谢您让我理解了什么是“竞态条件”。 - Shittu Joseph Olugbenga

0
在我的情况下,我尝试使用托管身份并在ConfigureServices中构造了一个SqlConnection,然后添加了AddDbContext<..>(o => o.UseSqlServer(sqlConnection); 这导致了奇怪的行为。将代码移动到Context OnConfiguring中解决了问题。
        var sqlConnection = new SqlConnection(Startup.Configuration.GetConnectionString("xxx"));
        optionsBuilder.UseSqlServer(sqlConnection);

0

我经常遇到这种情况。 我一直通过使用锁定关键字来解决。

lock (_context)
{
      var user = _context.users.first(x => x.Id == userId);
}

这将锁定对象(即_context)在当前线程中的使用,因此没有其他线程可以同时访问同一实例,因此不会出现任何问题。


0

@Dmitry的回答指引了我正确的方向。对于我来说,我在.NETCORE的中间件上遇到了这个问题。解决这个问题的方法是在我的中间件的Invoke方法中解决IUnitOfWork接口。

我做了类似以下的事情

public Task Invoke(HttpContext context)
{
    _unitOfWork = (IUnitOfWork)context.RequestServices.GetService(typeof(IUnitOfWork));

       //Some checks            

        var apiKey = context.Request.Headers["X-ApiKey"][0];

        var clientApp = _unitOfWork.ClientApplicationsRepository.Items.FirstOrDefault(s => s.ApiKey == apiKey);

     //Some other code...

    return _next(context);
}

这个答案缺乏太多上下文,对任何人都没有帮助。读者可能会认为你的IUnitOfWork是由你的DbContext子类实现的接口,但是你如何在多个线程中执行同一个实例还不清楚。 - Derek Greer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接