Refining My Data Bucket Class

Refining My Data Bucket Class

Last year I wrote a post showing you how to combine the Unit of Work pattern and the repository pattern into one elegant class that I call the DataBucket. In that post the data bucket looked like this:

public interface IDataBucket
{
    IBookRepository Books{ get; }
    void SaveChanges();
}

public class DataBucket : IDataBucket
{
    DataContext _dataContext;
    
    public DataBucket(DataContext dataContext)
    {
      	_dataContext = dataContext
    }
    
    private IBookRepository _bookRepository;
    public IBookRepository Books
    {
        get
        {
        	if (_bookRepository == null)
            	_bookRepository = new EFBookRepository(_dataContext);
          	return _bookRepository;
        }
    }
    
    public void SaveChanges()
    {
    	_dataContext.SaveChanges();
    }
}

The great thing about the data bucket is that it combines the repository pattern with the unit of work pattern. We're basically rebuilding the Entity Framework data context object with our data bucket class except we can unit test code that depends on our repositories with ease. One major downside to the data bucket above is that it is aware of repository concrete classes because it creates new instances of them manually when the property gets accessed. The lazy-loaded repository properties are great, but what if we want to have lazy-loading AND allow Ninject to resolve the repositories so that the data bucket doesn't have to be aware of concrete repository types?

We don't want to inject every single repository into the data bucket constructor; that would kill our lazy-loading feature as well as make the constructor ridiculously difficult to read and maintain. Luckily, I learned a nifty trick with Ninject recently that will allow us to do this easily and also cuts down on the amount of code needed for each repository property in the data bucket. You can have Ninject inject its own instance of IKernel, the Ninject object that exposes a direct API for resolving dependencies. We then simply ask the Ninject kernel to resolve our repository when the property is accessed.

public interface IDataBucket
{
    IBookRepository Books{ get; }
    void SaveChanges();
}

public class DataBucket : IDataBucket
{
	DataContext _dataContext;
    IKernel _kernel;
    
    public DataBucket(IKernel kernel, DataContext dataContext)
    {
        this._dataContext = dataContext
        this._kernel = kernel;
    }
    
    public IBookRepository Books { get { return _kernel.Get<IBookRepository>(); } }
    
    public void SaveChanges()
    {
    	_dataContext.SaveChanges();
    }
}

That's it! DataBucket is no longer aware of the repository implementing classes. DataBucket looks a lot like the original Entity Framework data context now. _db.Books.Get(bookId) should be pretty easy to read when consuming our data bucket API. The biggest difference is that all repositories and even the data bucket can now be easily mocked up in testing.

Multiple Data Contexts Problem

There is one gotcha that I haven't mentioned yet, but it can easily cause confusion. In both the old and new versions of the data bucket class we have a dependency on the Entity Framework data context. In the old version we passed our instance of the data context directly into the repository that we were instantiating. In the new version we are asking Ninject for our repositories so Ninject will be responsible for injecting the data context into them. There is one small issue with this that took me a while to figure out.

By default when Ninject sees a dependency on a concrete type it will attempt to instantiate it even if you don't have it mapped in the Ninject kernel. Because of this I didn't bother to create a binding for the Entity Framework data context object; I knew Ninject would find it and instantiate it for me. However, the default behavior is to instantiate dependencies in singleton scope. This means that it creates a new instance for every dependency. Remember that both our DataBucket and repository classes depend on the data context. This causes the data bucket to have a different instance of the data context than the repositories. Calling _db.Books.Delete(book); removes the specified book entity from the books collection on the data context handed to the book repository. Then you call _db.SaveChanges(); to persist those changes to the database right?

What you may not know is that you are calling SaveChanges() on a different instance of the Entity Framework data context. This is very frustrating if you don't understand what is going on because it does not produce an error. It is perfectly valid to make changes on one data context and then call SaveChanges() on another one, it just means that the changes made to the first data context won't be saved. It took me almost an hour to figure out why my changes weren't saving to the database and I wasn't getting any errors at all.

The fix is really easy, you just have to provide a binding for the data context to Ninject so that Ninject will use the same instance of the data context wherever it injects it. Just open up NinjectWebCommon.cs (or wherever else you may be storing your Ninject bindings) and add the following line:

kernel.Bind<DataContext>().ToSelf().InRequestScope();

ToSelf() is just a shortcut for .To<DataContext>() since our binding is just for a concrete type and not an interface you can just use ToSelf(). For the most part this binding is pointless because Ninject does this behavior by default. The real key is the InRequestScope() because that is what tells Ninject to hang onto the same instance of the data context for the entire duration of the request. Now when Ninject resolves IDataBucket and IBookRepository they will both be handed the same instance of the data context and your changes will be properly saved to the database.

It can be a somewhat complicated problem to follow, but the fix is easy. I hope this was helpful for you.