.NET Core 2.1 DbContext ObjectDisposeException 依赖注入

JC9*_*C97 3 c# dependency-injection asp.net-core

我正在使用 .NET Core 2.1 和实体框架制作一个 n 层 MVC 应用程序。还有一个托管 MQTT 队列,我的应用程序作为客户端进行侦听。我还使用依赖注入。这非常有效,直到一条消息被推送到队列并且我想将该消息保存到数据库。一旦发生这种情况,我会收到以下ObjectDisposedException错误消息:

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

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

启动.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
}
Run Code Online (Sandbox Code Playgroud)

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.
}
Run Code Online (Sandbox Code Playgroud)

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

存储库.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);
}
Run Code Online (Sandbox Code Playgroud)

数据库上下文.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");
    }
}
Run Code Online (Sandbox Code Playgroud)

我读过这篇文章,但这些情况似乎都不适合我。当我打印堆栈跟踪时,Dispose()它发生在Main()方法中,所以它并没有真正帮助我。

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

提前致谢!

Kir*_*kin 5

IServiceProvider传入的实例是Configurescoped,这意味着它在完成后由框架处置Configure- 它创建的任何范围服务也会在此过程中处置。

在您的示例中,您请求一个实例IIdeationManager(其范围是),然后尝试在您的类中使用它MqttBroker(实际上是一个singleton)。当您尝试使用 的实现时IIdeationManager,由 DI 创建和连接的作用域实例已被释放,因此会引发异常。CityOfIdeasDbContextObjectDisposedException

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

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

    // Do something with ideationManager.
}

// scope and all created disposable services have been disposed.
Run Code Online (Sandbox Code Playgroud)

当您请求实现 时IIdeationManager,DI 系统(最终)发现它需要一个范围CityOfIdeasDbContext并为您创建一个范围。一旦scope被处置,该CityOfIdeasDbContext实例也被处置。

为了使其在您的示例中工作,您MqttBroker可以将 的一个实例IServiceProvider放入其构造函数中,并使用它来创建我上面显示的范围(IConfiguration鉴于它本身是单例,它仍然可以按原样使用)。

IServiceProvider应该传递到MqttBroker类中的实例不应该是传递IServiceProvider的实例Configure- 这已经是范围内的,并且正如我所描述的,将在完成后被清理Configure,这确实是您开始遇到的问题。为此,请使用app.ApplicationServices,它是根提供程序且不受作用域限制。