C# .NET Core 2.1 DbContext ObjectDisposedException依赖项注入

C# .NET Core 2.1 DbContext ObjectDisposedException依赖项注入,c#,asp.net-core,dependency-injection,C#,Asp.net Core,Dependency Injection,我正在使用.NETCore2.1和EntityFramework制作一个n层MVC应用程序。还有一个托管的MQTT队列,我的应用程序作为客户机侦听该队列。我还使用依赖注入。这非常有效,直到消息被推送到队列并且我想将该消息保存到db。一旦发生这种情况,我会收到以下ObjectDisposedException错误消息: 无法访问已释放的对象。此错误的一个常见原因是处理通过依赖项注入解析的上下文,然后在应用程序的其他位置尝试使用相同的上下文实例。如果对上下文调用Dispose(),或将上下文包装到u

我正在使用.NETCore2.1和EntityFramework制作一个n层MVC应用程序。还有一个托管的MQTT队列,我的应用程序作为客户机侦听该队列。我还使用依赖注入。这非常有效,直到消息被推送到队列并且我想将该消息保存到db。一旦发生这种情况,我会收到以下
ObjectDisposedException
错误消息:

无法访问已释放的对象。此错误的一个常见原因是处理通过依赖项注入解析的上下文,然后在应用程序的其他位置尝试使用相同的上下文实例。如果对上下文调用Dispose(),或将上下文包装到using语句中,则可能会发生这种情况。如果您使用的是依赖项注入,那么应该让依赖项注入容器处理上下文实例。对象名称:“xxxDbContext”

我可以单击“继续”,然后应用程序继续工作。他只在从队列接收到的第一条消息上抛出异常。控制器/管理器/存储库的每一个其他操作都可以正常工作。我的代码如下:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddDefaultIdentity<User>()
            .AddEntityFrameworkStores<xxxDbContext>();

    services.AddDbContext<xxxDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
    ));

    // Some identity configuration omitted here

    services.AddScoped<IIdeationRepository, IdeationRepository>();
    services.AddScoped<IIdeationManager, IdeationManager>();
    // Some other DI configuration omitted as well.
}

public Configure(IApplicationBuilder app, IHostingEnvironment env,
    IApplicationLifetime applicationLifetime, IServiceProvider serviceProvider)
{
    // Start MQTT
    var broker = new MqttBroker(serviceProvider.GetService<IIdeationManager>(),
        serviceProvider.GetService<IConfiguration>());

    // On application exit terminate MQTT to make sure the connection is ended properly
    applicationLifetime.ApplicationStopping.Register(() => broker.Terminate());

    // Some default http pipeline code omitted
}
管理器只是将其直接发送到存储库,在那里会出现错误消息

Repository.cs

private xxxDbContext ctx;

public IdeationRepository(xxxDbContext xxxDbContext)
{
    this.ctx = xxxDbContext;
}

// This method crashes with the error
public IdeationReply ReadIdeationReply(int id)
{
    return ctx
        .IdeationReplies
        .Include(r => r.Votes)
        .FirstOrDefault(r => r.IdeationReplyId == id);
}
DbContext.cs

public class xxxDbContext : IdentityDbContext<User>
{
    public DbSet<Ideation> Ideations { get; set; }
    // Some more dbsets omitted

    public CityOfIdeasDbContext(DbContextOptions<CityOfIdeasDbContext> options) 
        : base (options)
    {
        CityOfIdeasDbInitializer.Initialize(this, dropCreateDatabase: false);
    } 

    // In configuring I just create a logger, nothing special

    // In OnModelCreating I just setup some value converters for other tables
    // than the ones I need here

    internal int CommitChanges()
    {
        if (delaySave)
        {
            int infectedRecords = base.SaveChanges();       
            return infectedRecords;
        }

        throw new InvalidOperationException(
            "No UnitOfWork present, use SaveChanges instead");
    }
}
公共类xxxDbContext:IdentityDbContext
{
公共数据库集思想{get;set;}
//省略了更多的数据库集
公共城市FideAsDbContext(DbContextOptions)
:基本(选项)
{
CityOfIdeasDbInitializer.Initialize(此,dropCreateDatabase:false);
} 
//在配置中,我只创建了一个记录器,没有什么特别的
//在OnModelCreating中,我只是为其他表设置了一些值转换器
//比我这里需要的要多
内部int CommitChanges()
{
如果(延迟保存)
{
int infectedRecords=base.SaveChanges();
返回感染记录;
}
抛出新的InvalidOperationException(
“不存在工作单元,请改用SaveChanges”);
}
}
我读过,但这些情况似乎都不适用于我。当我在
Dispose()
中打印stacktrace时,它发生在
Main()
方法中,因此它对我没有什么帮助

有人知道如何解决这个问题,或者我可以在哪里搜索解决这个问题吗


提前谢谢

传递到
Configure
中的
IServiceProvider
实例是作用域的,这意味着它在
Configure
完成后由框架处理-它创建的任何作用域服务也会在此过程中处理

在您的示例中,您正在请求
IIdeationManager
(其作用域)的一个实例,然后尝试在
MqttBroker
类中使用该实例(实际上,它是一个单例)。当您尝试使用
IIdeationManager
的实现时,DI创建并连接的
CityOfIdeasDbContext
的作用域实例已被释放,因此引发了
ObjectDisposedException
异常

为了解决这个问题,您可以使用一个通用模式,当一个单例需要访问一个作用域服务时可以使用该模式:创建一个作用域,解析该服务,使用该服务,然后释放该作用域。粗略地说,这看起来有点像这样:

using (var scope = serviceProvider.CreateScope())
{
    var ideationManager = scope.ServiceProvider.GetService<IIdeationManager>();

    // Do something with ideationManager.
}

// scope and all created disposable services have been disposed.
使用(var scope=serviceProvider.CreateScope())
{
var ideationManager=scope.ServiceProvider.GetService();
//与ideationManager合作。
}
//范围和所有创建的一次性服务已被处置。
当您请求实现
IIdeationManager
时,DI系统发现(最终)它需要一个作用域
CityOfIdeasDbContext
,并为您创建一个。一旦
scope
被释放,这个
CityOfIdeasDbContext
实例也被释放

为了在您的示例中实现这一点,您的
MqttBroker
可以将
IServiceProvider
的一个实例放入其构造函数中,并使用它来创建我上面所示的范围(假定它本身是单例,它仍然可以采用
IConfiguration


应该传递到
MqttBroker
类中的
IServiceProvider
实例不应该是传递到
Configure
中的
IServiceProvider
——这已经是范围,如我所述,将在
Configure
完成后进行清理,这才是你开始考虑的问题。为此,请使用
app.ApplicationServices
,它是根提供程序,不受作用域限制。

我以前也遇到过同样的问题,我试图处理dbcontext对象,如.net经典分层结构存储库设计模式案例,但在.net core中使用足以使其具有作用域的内容,这就解决了问题。因为它会为每个请求进行dispose,所以您不需要手动处理dbcontext。此外,IServiceCollection中的adddbcontext方法将其范围作为默认值实现

您在何处注入
IdeationRepository
以及如何使用它,在
Startup.cs
中向我们展示服务/回购注册。更有可能的是,您正在实现
IDisposable
并处理上下文,即使上下文正在注入。这只是一个猜测,因为您尚未在此处发布任何内容的完整代码。如果是这样的话,停下来。您应该只处理类拥有的东西,也就是说,它会显式地更新它们。如果它被注入,则类不拥有它,因此不应处置它。@ingvar更新了问题。谢谢你的反应。@ChrisPratt谢谢你的反应。我从来没有在代码中显式调用Dispose()。您使用的是工作单元模式吗?另外,如果
System.ObjectDisposedException
发生在哪里,请向我们说明此方法代码工作得非常完美。现在唯一的事情就是我创造了
using (var scope = serviceProvider.CreateScope())
{
    var ideationManager = scope.ServiceProvider.GetService<IIdeationManager>();

    // Do something with ideationManager.
}

// scope and all created disposable services have been disposed.