.NET Core 2.1 DbContext ObjectDisposedException依赖项注入

时间:2019-04-01 17:22:33

标签: c# asp.net-core dependency-injection

我正在使用.NET Core 2.1和Entity Framework创建n层MVC应用程序。还有一个托管的MQTT队列,我的应用程序在该队列上作为客户端侦听。我还利用了依赖注入。这非常有效,直到将消息推送到队列并且我想将该消息保存到数据库。一旦发生这种情况,我会收到以下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
}

MqttBroker.cs

public MqttBroker(
    [FromServices] IIdeationManager ideationManage,
    [FromServices] IConfiguration configuration)
{
    _ideationManager = ideationManager;
    _configuration = configuration;

    Initialize();
}

    // Some code where I just parse the message and on receive send it to the
    // ideation manager, this just works so I omitted it.
}

管理器只是将其直接发送到发生错误消息的存储库中。

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");
    }
}

我已经读过this,但这些情况似乎都不适合我。而且,当我在Dispose()中打印stacktrace时,它是在Main()方法中发生的,因此它并没有真正帮助我。

有人知道如何解决这个问题,或者在哪里可以找到解决办法?

谢谢!

2 个答案:

答案 0 :(得分:2)

传递到IServiceProvider的{​​{1}}实例是作用域的,这意味着Configure完成后,它会被框架处置-任何 scoped 服务也会在此过程中被处理。

在您的示例中,您请求一个Configure的实例(作用域为 ),然后尝试在您的IIdeationManager类中使用该实例(有效,一个 singleton )。在您尝试使用MqttBroker的实现时,由DI创建并关联的IIdeationManager scoped 实例已被废弃,因此{{1} }引发异常。

为了解决此问题,您可以采用一种通用模式,该模式在单例需要访问有范围的服务时使用:创建一个范围,解析该服务,使用该服务,然后处置该范围。松散地,看起来会像这样:

CityOfIdeasDbContext

当您请求实现ObjectDisposedException时,DI系统会(最终)看到它需要一个范围限定的using (var scope = serviceProvider.CreateScope()) { var ideationManager = scope.ServiceProvider.GetService<IIdeationManager>(); // Do something with ideationManager. } // scope and all created disposable services have been disposed. 并为您创建一个。一旦IIdeationManager被处置,该CityOfIdeasDbContext实例也将被处置。

为了使它在您的示例中起作用,您的scope可以将CityOfIdeasDbContext的一个实例放入其构造函数中,并将其用于创建上面显示的范围(它仍然可以包含{假设{1}}本身是一个单例。

应该传递给MqttBroker类的IServiceProvider实例应该 not 不是传递给IConfiguration的{​​{1}}-这是IServiceProvider完成后,已经确定了作用域,并且已经清理过了,这实际上是您刚开始遇到的问题。为此,请使用MqttBroker,它是根提供程序,没有作用域。

答案 1 :(得分:0)

在我尝试处置dbcontext对象(如.net经典分层结构存储库设计模式案例)之前,我曾遇到过相同的问题,但是在.net核心中,足以使它解决问题。因为它为每个请求处理,所以您不需要手动处理dbcontext。另外,IServiceCollection中的adddbcontext方法将其范围设置为默认值。 Reference