Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DbContext has been disposed and autofac

I have a controller:

private readonly ILogger _logger;    
private readonly IRepository _repository;

public HomeController(ILogger logger, IRepository repository)
{
   _logger = logger;
   _repository = repository;
}

This is the repository:

public class EfRepository : IRepository
{
    // ...methods for add, delete, update entities
    // ....

    public void Dispose()
    {
         if (this._context != null)
         {
             this._context.SaveChanges();
             (this._context as IDisposable).Dispose();
             this._context = null;
         }
    }
}

Finally, registration types in IoC:

_builder.RegisterType<Logger>().As<ILogger>();
_builder.RegisterType<EfRepository>().As<IRepository>().WithParameter("context", new PcpContext());

When I run the application I get this error:

The operation cannot be completed because the DbContext has been disposed.

I tried to change registration EfRepository like this:

_builder.RegisterType<EfRepository>()
   .As<IRepository>()
   .WithParameter("context", new PcpContext()).InstancePerLifetimeScope();

In this case the first request finish, but when trying to open other pages, I get the error again. Where is the problem?

like image 204
user1260827 Avatar asked Feb 17 '13 09:02

user1260827


1 Answers

When using the WithParameter method, the parameter instance will be the same for every resolved object. So with .WithParameter("context", new PcpContext()) you are effectively using the same instance of the PcpContext class for any resolved instance of IRepository.

With your current code, when an IRepository instance is disposed, it will also dispose that PcpContext instance. Then any subsequent attempt to resolve an IRepository will receive the PcpContext instance that was disposed. You need a way to receive a fresh new instance of the EF DbContext on each Http Request that is disposed of at the end of the request.

One option could be to register a code block for the IRepository so that code block is executed every time an IRepository needs to be resolved:

_builder.Register<IRepository>(c => new EfRepository(new PcpContext()))

A better option would be to create a new IDatabaseContext abstraction, updating EfRepository so it depends on the new IDatabaseContext abstraction instead of the PcpContextclass (Which may already be the case :) ).

The implementation class for IDatabaseContext will be your PcpContext class, which must inherit from the EF DbContext and probably receive the connection string as a parameter.

public class EfRepository : IRepository
{
    private readonly IDatabaseContext _context;

    public EfRepository(IDatabaseContext context)
    {
        _context = context;
    }

    ...methods for add, delete, update entities

    //There is no longer need for this to be disposable.
    //The disaposable object is the database context, and Autofac will take care of it
    //public void Dispose()
}

public interface IDatabaseContext : IDisposable 
{
    ... declare methods for add, delete, update entities
}

public class PcpContext: DbContext, IDatabaseContext 
{
    public EntityFrameworkContext(string connectionString)
        : base(connectionString)
    {
    }

    ...methods exposing EF for add, delete, update entities

    //No need to implement IDisposable as we inherit from DbContext 
    //that already implements it and we don´t introduce new resources that should be disposed of
}

This gets better with the idea of using an IoC container and leaving the burden of lifetime management to them. Now your Repository class does not need to be disposable nor needs to manage and dispose of its IDatabaseContext dependency. It is Autofac who will keep track of the context instance and dispose of it when appropriate.

For the same reason, you probably want to use InstancePerLifetimeScope with the database context dependency. That would mean the same EF context is shared for every repository instance on the same Http request and is disposed at the end of the request.

_builder.RegisterType<EfRepository>()
   .As<IRepository>();

_builder.RegisterType<PcpContext>()
   .As<IDatabaseContext>()
   .WithParameter("connectionString", "NameOfConnStringInWebConfig")
   .InstancePerLifetimeScope();
like image 105
Daniel J.G. Avatar answered Oct 29 '22 12:10

Daniel J.G.