You need to enable JavaScript to run this app. Discord.NET 依赖注入注册AddDbContext后DatabaseContext被共享的解决方法
优惠活动
大模型
产品
解决方案
定价
更多

Discord.NET 依赖注入注册AddDbContext后DatabaseContext被共享的解决方法

问题根因定位
  • AddDbContext 默认将 DatabaseContext 注册为 Scoped(作用域生命周期) 服务,设计上绑定单次任务的执行周期,任务完成后自动释放。
  • 当前代码在调用命令执行方法时,直接传入了根容器的全局IServiceProvider。当没有为单次命令执行创建独立作用域时,从根容器解析Scoped服务会触发DI的默认行为:将该Scoped服务提升为根容器单例,导致所有命令请求共享同一个DatabaseContext实例,出现跨请求的实体缓存复用问题。
  • Discord.NET 的CommandService本身不会自动为每次命令执行创建DI作用域,需要开发者手动管控作用域的创建与释放流程。
调整实现方案
  1. 现有服务注册逻辑不需要修改,AddDbContext 的默认Scoped注册策略完全匹配需求,无需调整为其他生命周期。
  2. 修改命令执行入口,为每一次用户触发的命令单独创建独立DI作用域,从作用域内解析服务传入命令执行流程。命令执行完成后,自动释放整个作用域,连带释放作用域内创建的DatabaseContext实例。

修改后的HandleCommandAsync代码如下:

private async Task HandleCommandAsync(SocketMessage messageParam)
{
    var message = messageParam as SocketUserMessage;
    if (message == null) return;

    int argPos = 0;

    if (!(message.HasStringPrefix("b.", ref argPos) ||
        message.HasMentionPrefix(_client.CurrentUser, ref argPos)) ||
        message.Author.IsBot)
        return;

    var context = new SocketCommandContext(_client, message);

    // 为单次命令执行创建独立DI作用域
    using var commandScope = _services.CreateScope();
    // 传入当前作用域的服务提供者执行命令
    await _commands.ExecuteAsync(context, argPos, commandScope.ServiceProvider);
}
  1. 现有UserModule的实现不需要做任何调整。模块实例、注入的DatabaseContext都会从当前命令对应的独立作用域中解析,保证每一次命令执行拿到的都是全新的DatabaseContext实例,命令结束后随着作用域释放自动完成资源回收。

注意:不要为了实现独立实例的目标,把DatabaseContext改成Transient或Singleton生命周期。Scoped是EFCore官方推荐的数据库上下文生命周期,配合独立作用域可以自动完成实例创建、缓存隔离、资源释放的全流程管控,不会出现数据库连接泄漏或缓存不一致的问题。

效果验证

调整完成后,连续多次执行b.user命令时,打印的ContextId会依次变化、互不重复。DatabaseContext的生命周期完全和单次命令执行绑定,不会跨请求缓存实体数据,也就不会出现和其他共享数据库的应用之间的数据缓存不一致问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.30 20:27:03