简体   繁体   中英

.NET Core 2.1 DbContext ObjectDisposedException Dependency Injection

I'm making an n-tier MVC application using .NET Core 2.1 and Entity Framework. There is also a hosted MQTT queue on which my application listens as a client. I also make use of Dependency Injection. This works perfectly, until a message is pushed to the queue and I want to save that message to the db. Once that happens I get following ObjectDisposedException error message:

Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. Object name: 'xxxDbContext'.

I can click continue, after which the application just continues to work. He only throws the exception on the first message received from the queue. Every other action with the controllers/managers/repositories works just fine. My code is as follows:

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.
}

The manager just sends it directly to the repository, where the error message occurs.

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

I've read this but none of these situations seem to apply to me. And when I print the stacktrace in Dispose() it happens in the Main() method, so it doesn't really help me.

Anyone an idea how to solve or where I can search to solve this?

Thanks in advance!

The IServiceProvider instance that's passed into Configure is scoped , which means that it gets disposed by the framework after Configure completes - any of the scoped services that it creates are also disposed during this process.

In your example, you're requesting an instance of IIdeationManager (which is scoped ) and then attempting to use that in your MqttBroker class (which is, effectively, a singleton ). By the time you attempt to use your implementation of IIdeationManager , the scoped instance of CityOfIdeasDbContext that was created and hooked up by DI has been disposed and so an ObjectDisposedException exception is thrown.

In order to resolve this, you can employ a general pattern that gets used when a singleton needs access to a scoped service: Create a scope, resolve the service, use the service and then dispose of the scope. Loosely, that would look a little bit like this:

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

    // Do something with ideationManager.
}

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

When you request an implementation of IIdeationManager , the DI system sees that (ultimately) it needs a scoped CityOfIdeasDbContext and creates one for you. Once scope is disposed, this CityOfIdeasDbContext instance is also disposed.

In order for this to work in your example, your MqttBroker can take an instance of IServiceProvider into its constructor and use that for creating the scope I've shown above (it can still take IConfiguration as-is given that that itself is a singleton).

The IServiceProvider instance that should be passed into the MqttBroker class should not be the IServiceProvider that is passed into Configure - this is already scoped and, as I've described, will be cleaned up after Configure completes, which is really the issue you've started out with. For this, use app.ApplicationServices , which is the root provider and is not scoped.

I was faced same problem before also I was trying to dispose dbcontext object like .net classic layered structure repository design pattern cases but in .net core enough to make it scoped resolves problem. Because it dispose for each request so you dont need to dispose dbcontext manually. In addition, adddbcontext method in IServiceCollection implements it scoped as default. Reference

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM