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

如何解决配置基于Entity Framework的Microsoft ConversationBot自定义存储的DI异常?

解决Teams通知Bot中EF存储的ObjectDisposedException异常

问题原因

你排查的结论完全正确:NotificationMiddleware是单例生命周期,而你注册的IConversationReferenceStore是Scoped生命周期。当Scoped的存储实例被单例中间件持有后,第一个请求结束时EF上下文会被DI容器自动释放,但中间件仍保留着这个已释放的存储实例,后续请求调用存储方法时就会触发System.ObjectDisposedException。

解决方案

核心思路是避免单例服务直接持有Scoped实例,改为每次需要使用时动态获取新的Scoped实例,以下是两种可行方案:

方案1:通过服务工厂延迟获取Scoped存储实例

这种方式既保留EF上下文的Scoped生命周期(避免并发问题),又解决单例中间件持有过期实例的问题。

  1. 创建存储工厂类
public class ConversationReferenceStoreFactory
{
    private readonly IServiceProvider _serviceProvider;

    public ConversationReferenceStoreFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IConversationReferenceStore GetScopedStore()
    {
        using var scope = _serviceProvider.CreateScope();
        return scope.ServiceProvider.GetRequiredService<IConversationReferenceStore>();
    }
}
  1. 修改DI注册逻辑
builder.ConfigureServices(services =>
{
    // 保持Scoped存储注册不变
    services.AddScoped<IConversationReferenceStore, ConversationReferenceStore>();
    // 注册工厂为单例
    services.AddSingleton<ConversationReferenceStoreFactory>();

    // 原有Bot相关注册代码...

    // 调整ConversationBot的注册,使用工厂包装的存储
    services.AddScoped(sp =>
    {
        var storeFactory = sp.GetRequiredService<ConversationReferenceStoreFactory>();
        var options = new ConversationOptions()
        {
            Adapter = sp.GetRequiredService<CloudAdapter>(),
            Notification = new NotificationOptions
            {
                BotAppId = botId,
                Store = new LazyScopedConversationStore(storeFactory)
            }
        };

        return new ConversationBot(options);
    });
});
  1. 实现包装类适配接口
public class LazyScopedConversationStore : IConversationReferenceStore
{
    private readonly ConversationReferenceStoreFactory _factory;

    public LazyScopedConversationStore(ConversationReferenceStoreFactory factory)
    {
        _factory = factory;
    }

    public async Task<bool> Add(string key, ConversationReference reference, ConversationReferenceStoreAddOptions options, CancellationToken cancellationToken)
    {
        var store = _factory.GetScopedStore();
        return await store.Add(key, reference, options, cancellationToken);
    }

    // 实现IConversationReferenceStore的其他方法,逻辑同上
    public async Task<IEnumerable<ConversationReference>> ListAsync(string channelId, CancellationToken cancellationToken)
    {
        var store = _factory.GetScopedStore();
        return await store.ListAsync(channelId, cancellationToken);
    }

    // 省略其他接口方法实现...
}

方案2:将EF上下文改为Singleton(不推荐)

可以临时将MyDbContext注册为Singleton,让上下文生命周期与单例中间件一致,但这会导致EF上下文在应用全程共享,可能引发并发冲突、内存泄漏等问题,仅适合测试场景,禁止生产环境使用:

services.AddDbContext<MyDbContext>(options => 
    options.UseSqlServer("your_connection_string"), 
    ServiceLifetime.Singleton);

关键注意事项

  • 单例服务绝对不能持有Scoped/Transient实例,这是DI生命周期管理的核心规则。
  • 使用IServiceProvider.CreateScope()获取Scoped实例时,必须用using包裹Scope,确保实例使用后被正确释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 21:10:04