I would like to put the following design to your consideration. This is the pattern I use to follow when I'm building a Model and Data Access Layer for an MVC Application (using Entity Framework 5). I'm looking for any advises about how to improve me design (feel free to provide a new one) because I feel that it can be done better.
I want to focus on a model to be consumed for MVC Applications using Entity Framework 5 Code First, because this way we have a more specific scenario.
First I have the interface IEntity
. All entities in my model implement it and this way is ensured that all entities have a numeric key.
interface IEntity
{
int Id { get; set; }
}
Next I define entities like this:
class Person : IEntity
{
public virtual int Id {get;set;}
public virtual string Name {get;set;}
public virtual int Age {get;set;}
public virtual int PetId {get;set;}
public virtual Pet Pet {get;set;}
}
class Pet : IEntity
{
public virtual int Id {get;set;}
public virtual string Name {get;set;}
public virtual int OwnerId {get;set}
public virtual Person Owner {get;set}
}
This is the important part:
I use a mix between Unit of Work and Repository to build a Data Access layer that let me decouple the Business logic from my data.
The abstract definition of a Unit of Work is like this:
public interface IUnitOfWork
{
int SaveChanges();
}
In this case I need a repository for People and other for Pets.
interface IPersonRepository
{
// Here I declare each method that my application needs.
// Each database dependent implementation will implement those methods.
void Add(Person person);
void Edit(Person person);
void Remove(Person person);
void RemoveById(int id);
IQueriable<Person> GetByName(string name);
}
interface IPetRepository
{
// Similar to IPersonRepository
}
Now in my case I build a concrete implementation of my repositories and UoW using Entity Framework.
public class EFUnitOfWork : DbContext, IUnitOfWork
{
public DbSet<Person> People { get; set; }
public DbSet<Pet> Pets { get; set; }
}
public class EFPersonRepository : IPersonRepository
{
private readonly EFUnitOfWork context;
public EFPersonRepository(EFUnitOfWork context)
{
this.context = context;
}
public Directory GetById(int id)
{
return context.People.Find(id);
}
// Other methods of the interface IPersonRepository.
}
public class EFPetRepository : IPetRepository
{
// Similar to EFPersonRepository.
}
I use Ninject as my Dependency Injection framework. In configuration I bind each abstract definition (interface) with the concrete implementation. I'm doing the binding using InRequestScope
and this way all repositories that are instantiated on each Http Request share the same DbContext
(through the same UnitOfWork). I'm concerned if InRequestScope
really does what I'm expecting because sometimes I get an exception that says that the DbContext
has been disposed.
public class NinjectControllerFactory : DefaultControllerFactory
{
private readonly IKernel kernel;
public NinjectControllerFactory()
{
kernel = new StandardKernel();
AddBindings();
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, System.Type controllerType)
{
return controllerType == null ? null : (IController)kernel.Get(controllerType);
}
private void AddBindings()
{
kernel.Bind<EFUnitOfWork>().To<EFUnitOfWork>().InRequestScope();
kernel.Bind<IPersonRepository>().To<EFPersonRepository>().InRequestScope();
kernel.Bind<IPetRepository>().To<EFPetRepository>().InRequestScope();
}
}
And that's all.
Remarks
Pros:
- I can replace my concrete implementation of the data access by implementing a new set of repositories and configure the bindings.
- The business logic only see the methods exposed in repositories. This prevent using complex queries outside the data access layer.
Cons:
- The master object (a mean the context in case of EF) that has all functionality remains hidden. So we don't have any information about tracking entities, etc, in the code outside the repository. Sometimes that info is useful when debugging.
- Each query to the data must be declared in the abstract definition of the repository and then implemented on all concrete implementation. Which I think is good but someone may think that this is tedious.