.NET Core 2.1 DbContext 的 ObjectDisposedException 依赖注入问题

3
我正在使用.NET Core 2.1和Entity Framework制作一个n层MVC应用程序。此外,我的应用程序还作为客户端侦听托管的MQTT队列。我还利用了Dependency Injection。这很完美,直到将消息推送到队列并想将该消息保存到数据库时。一旦发生这种情况,我就会收到以下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()中打印堆栈跟踪时,它发生在Main()方法中,所以这并没有真正帮助我。
有人有解决方法或者我可以搜索解决方法的地方吗?
提前致谢!

1
你在哪里注入 IdeationRepository 并且如何使用它?请展示在 Startup.cs 中的服务/仓库注册。 - ingvar
@ChrisPratt 感谢您的回应。我在代码中从未显式调用Dispose()方法。 - JC97
我将存储库和管理器从Scoped更改为Transient,但仍然出现错误。我不认为我可以更改DbContext add?我甚至尝试通过交换addIdentity和addDbContext的位置来解决问题,但没有成功。 - JC97
1
你是否正在使用工作单元模式?此外,System.ObjectDisposedException 在哪里发生,请向我们展示这个方法的代码。 - ingvar
让我们在聊天中继续这个讨论 - ingvar
显示剩余7条评论
2个回答

5
Configure方法中传入的IServiceProvider实例是范围的,这意味着在Configure方法完成后,框架将其处置 - 它创建的任何范围服务也将在此过程中被处置。
在您的示例中,您正在请求一个IIdeationManager实例(它是范围的),然后尝试在MqttBroker类(实际上是单例)中使用它。当您尝试使用IIdeationManager的实现时,DI创建并连接的范围CityOfIdeasDbContext实例已被处置,因此会抛出ObjectDisposedException异常。
为解决此问题,您可以使用一种常用模式,即当单例需要访问范围服务时:创建一个scope,解析服务,使用服务,然后处理scope。大致如下所示:
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 时,DI 系统会看到(最终)它需要一个作用域为 CityOfIdeasDbContext 的实例,并为您创建一个。一旦 scope 被释放,这个 CityOfIdeasDbContext 实例也将被释放。
为了使此示例正常工作,您的 MqttBroker 可以在其构造函数中使用 IServiceProvider 的实例来创建上述范围(它仍然可以像原来一样使用 IConfiguration,因为它本身是一个单例)。
应该传递给 MqttBroker 类的 IServiceProvider 实例不应该是传递给 ConfigureIServiceProvider - 这已经被分配了作用域,并且如我所描述的,在 Configure 完成后将被清理掉,这实际上就是您开始遇到的问题。为此,请使用 app.ApplicationServices,它是根提供程序,没有作用域。

完美运行。现在唯一的问题是我使用new创建了MQTTBroker,而不是通过DI:var broker = new MqttBroker(Configuration, app.ApplicationServices.GetService<IServiceProvider>()); 这样做的原因是我没有找到如何添加这个serviceProvider而不是configure的那个。感谢您的努力和解决方案! - JC97
另外,scope.GetService... 应该是 scope.ServiceProvider.GetService :) - JC97
很棒 - 感谢更新(我已经修复了答案)。我认为你应该可以直接将 app.ApplicationServices 传递到你的 MqttBroker 中,你试过了吗? - Kirk Larkin
似乎不能自动地通过 DI 进行操作,目前我只是使用 new,但如果有可能通过 DI 更加简洁地实现,那就太好了,但这不是必须的 ;) - JC97

0

我之前也遇到过同样的问题,当时我尝试按照传统的.net分层结构存储库设计模式处理方法来处理dbcontext对象的释放,但在.net核心中,只需要将其定义为scoped类型即可解决问题。因为它会在每个请求后进行自动释放,所以你无需手动释放dbcontext。另外,在IServiceCollection中添加adddbContext方法时,默认情况下它已实现了scoped类型。参考文献


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接