11

UPDATE

Found this little gem which helped me with DbContext Josh Kodroff - Making Entity Framework More Unit-Testable

Original

After doing a lot of research I finally decided to implement IOC using Autofac in my MVC5 EF6 project. Autofac's documentation has been helpful, but I'm still not sure about whether or not I need to call Dispose() either in my Controller or Service Class?

I'm not using an abstracted UOW and Generic Repository, but just relying on DbContext and DbSet<> provided in EF6. Here's a snippet of my classes.

My DbContext

public class ProductContext : DbContext
{
    public ProductContext() : base("ProductContext")
    {
    }

    public DbSet<Availability> Availability { get; set; }       
    public DbSet<Category> Categories { get; set; } 
    ....
 }

My Service Class

    public class ProductService : IProductService
{
    private ProductContext _db;
    public ProductService(ProductContext db)
    {
        _db = db;
    }

    public List<Product> GetProductsByCategory(string cleanCategory)
    {
        return _db.Products
             .Include(p => p.Options.Select(o => o.OptionGroup))
                 .Include(p => p.Associations.Select(a => a.AssociatedGroup))
                 .Include(p => p.Variations).Include(p => p.Manufacturer)
                 .Where(p => p.Active && p.Category.Name.ToUpper().Equals(cleanCategory)).ToList();
    }
    .....
}

My Service Interface

    public interface IProductService
{
    List<Product> GetProductsByCategory(string cleanCategory);
    ....
}

My Contoller

    public class ProductsController : Controller
{
    private IProductService _productService;
    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    //GET: Products/
    public ActionResult Index(string category)
    {
        if (String.IsNullOrEmpty(category))
        {
            return HttpNotFound();
        }

        string cleanCategory = urlScrubber(category);
        var viewModel = new ProductsVM();
        viewModel.ProductList = _productService.GetProductsByCategory(cleanCategory);
}

My Autofac Container

var builder = new ContainerBuilder();

        // Register your MVC controllers.
        builder.RegisterControllers(typeof(MvcApplication).Assembly);

        // REGISTER COMPONENTS HERE:
        builder.RegisterType<ProductContext>().AsSelf().InstancePerRequest();
        builder.RegisterType<ProductService>().As<IProductService>().InstancePerRequest();

        // Set the dependency resolver to be Autofac.
        var container = builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

I have removed Dispose() from the controller with the understanding that Autofac would handle the disposal of contexts that inherit from IDisposable. Since ProductContext inherits from DbContext which includes a Dispose() Method, this should work.

Do I need to include something like

builder.RegisterType<ProductContext>().As<DbContext>().InstancePerRequest();

or will my current container work as expected calling Dispose?

builder.RegisterType<ProductContext>().AsSelf().InstancePerRequest();

Thanks for any help, I'm having a hard time locating documentation using Autofac without a generic repository and UOW on top of DbContext similar to my current pattern.

SeanG80
  • 159
  • 1
  • 1
  • 10
  • This related question might be informative: https://stackoverflow.com/questions/10585478/one-dbcontext-per-web-request-why – Steven Oct 14 '15 at 19:58
  • Thanks Steven. I checked it out. I just need something specifically related to Autofac and EF6 DbContext Dispose(). I have stumbled across examples stacking another UnitOfWork and Repository on top of it with a DI Container, but nothing that just takes advantage of constructor injection that sticks with the DbContext provided. – SeanG80 Oct 14 '15 at 20:57
  • Note that in most times it's not needed to dispose `DbContext` since the underlying connection will be opened/closed automatically before/after use: [Do I always have to call Dispose() on my DbContext objects? Nope](http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html#.U9efX_ldXpo) – Tim Schmelter Jun 14 '16 at 10:41

2 Answers2

8

As per the doucmentation,

Autofac integration libraries standard unit-of-work lifetime scopes will be created and disposed for you automatically. Autofac’s ASP.NET MVC integration, a lifetime scope will be created for you at the beginning of a web request and all components will generally be resolved from there. At the end of the web request, the scope will automatically be disposed - no additional scope creation is required on your part.

So I think if your class implments IDisposable then Dispose() would be automatically called for such objects. So simply,

builder.RegisterType<ProductContext>().As<DbContext>().InstancePerRequest();

Would do the Disposal via object life scope management.

vendettamit
  • 14,315
  • 2
  • 32
  • 54
  • So registering ().As() ensures that Dispose() is called? I didn't see any documentation using DbContext without an interface so that is what made me question just whether registering ProductContext.AsSelf() would still call DbContext's Dispose? – SeanG80 Oct 14 '15 at 20:46
  • If `ProductContext` is `IDisposable` then integration libraries takes care of it by adding it to disposable items. I used your sample for the sake of providing sample. Simply `builder.RegisterType().InstancePerRequest(); ` would make no diffference unless `ProductContext` is `IDisposable`. see https://github.com/autofac/Autofac/blob/master/src/Autofac/Core/Resolving/InstanceLookup.cs – vendettamit Oct 14 '15 at 21:12
  • Not sure I understand whether you are saying my example is right or wrong. In my example ProductContext inherits from DbContext which has a Dispose(). ProductContext itself does not directly inherit IDisposable. So do I need to add .As() to ensure autofac discovers IDisposable or will just registering ProductContext itself allow autofac to discover IDisposable. – SeanG80 Oct 14 '15 at 21:56
  • Everything is working fine now locally, I just want to make sure autofac is doing it's magic during production an closing contexts so stale data isn't a problem as user traffic grows. – SeanG80 Oct 14 '15 at 22:00
  • 2
    It's simple as that.. DbContext is abstract class that implements IDisposable means It's dervied doesn't required to implement IDisposable explicitly. The Dispose would actually gets called on Dervied object. This is basic principle of Inheritance :) Anyways.. GoodLuck with your production stuff. Cheers!! – vendettamit Oct 14 '15 at 22:10
  • Thanks for the quick response. Good to know. I spent a lot of time researching containers and constructor injection. Once I implemented autofac and removed the dispose, I was scratching my head trying to figure out how it knew when to call dispose without explicit use in my service or controller. Thanks for the clarification. – SeanG80 Oct 14 '15 at 22:15
  • No problem. Glad it helped. :) Moreover you can also control it using OnRelease() extension during registration for customizing your disposal. – vendettamit Oct 14 '15 at 22:20
4

Autofac also supports using Func<> in constructor injection. For example, you can register your data context like normal:

builder.RegisterType<ProductContext>().As<IProductContext>();

and use it as follows in your ProductService:

public class ProductService : IProductService
{
    private IProductContext _dbCreator;
    public ProductService(Func<IProductContext> dbCreator)
    {
        _db = db;
    }

    public List<Product> GetProductsByCategory(string cleanCategory)
    {
        using (var dbCtx = _dbCreator())
        {

            return dbCtx.Products
                 .Include(p => p.Options.Select(o => o.OptionGroup))
                     .Include(p => p.Associations.Select(a => a.AssociatedGroup))
                     .Include(p => p.Variations).Include(p => p.Manufacturer)
                     .Where(p => p.Active && p.Category.Name.ToUpper().Equals(cleanCategory)).ToList();
        }
    }
    .....
}

Basically, your ProductService now has access to a Func<>(_dbCreator) that creates a new instance of your ProductContext based on your autofac registration every time it's called, allowing you to dispose the instance when you deem appropriate.

I realized after I wrote this that you don't have an IProductContext, I would usually recommend using this pattern, however, it isn't too important as far as your question is concerned. You can continue to use your current registration method for ProductContext and then just pass in a Func<ProductContext> instead of an IProductContext, i.e.,

builder.RegisterType<ProductContext>().AsSelf();

and

private ProductContext _dbCreator;
public ProductService(Func<ProductContext> dbCreator)

Sorry if the code doesn't compile, I didn't use an IDE... Hopefully it's close enough for you to get my point!

crunchy
  • 246
  • 1
  • 3
  • 12