This is the first time I am implementing a more domain-driven design approach. I have decided to try the Onion Architecture as it focuses on the domain rather than on infrastructure/platforms/etc.
In order to abstract away from Entity Framework, I have created a generic repository with a Unit of Work implementation.
The IRepository<T>
and IUnitOfWork
interfaces:
public interface IRepository<T>
{
void Add(T item);
void Remove(T item);
IQueryable<T> Query();
}
public interface IUnitOfWork : IDisposable
{
void SaveChanges();
}
Entity Framework implementations of IRepository<T>
and IUnitOfWork
:
public class EntityFrameworkRepository<T> : IRepository<T> where T : class
{
private readonly DbSet<T> dbSet;
public EntityFrameworkRepository(IUnitOfWork unitOfWork)
{
var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;
if (entityFrameworkUnitOfWork == null)
{
throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
}
dbSet = entityFrameworkUnitOfWork.GetDbSet<T>();
}
public void Add(T item)
{
dbSet.Add(item);
}
public void Remove(T item)
{
dbSet.Remove(item);
}
public IQueryable<T> Query()
{
return dbSet;
}
}
public class EntityFrameworkUnitOfWork : IUnitOfWork
{
private readonly DbContext context;
public EntityFrameworkUnitOfWork()
{
this.context = new CustomerContext();;
}
internal DbSet<T> GetDbSet<T>()
where T : class
{
return context.Set<T>();
}
public void SaveChanges()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
The Customer repository:
public interface ICustomerRepository : IRepository<Customer>
{
}
public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository
{
public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork)
{
}
}
ASP.NET MVC controller using the repository:
public class CustomerController : Controller
{
UnityContainer container = new UnityContainer();
public ActionResult List()
{
var unitOfWork = container.Resolve<IUnitOfWork>();
var customerRepository = container.Resolve<ICustomerRepository>();
return View(customerRepository.Query());
}
[HttpPost]
public ActionResult Create(Customer customer)
{
var unitOfWork = container.Resolve<IUnitOfWork>();
var customerRepository = container.Resolve<ICustomerRepository>();;
customerRepository.Add(customer);
unitOfWork.SaveChanges();
return RedirectToAction("List");
}
}
Dependency injection with unity:
container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
container.RegisterType<ICustomerRepository, CustomerRepository>();
Solution:
PROBLEMS?
Repository implementation (EF code) is very generic. It all sits in side the
EntityFrameworkRepository<T>
class. Concrete model repositories do not contain any of this logic. This saves me from writing tons of redundant code, but possibly sacrifices flexibility?The
ICustomerRepository
andCustomerRepository
classes are basically empty. They are purely there to provide abstraction. As far as I understand, this fits with the vision of the Onion Architecture, where infrastructure and platform-dependent code sits on the outside of your system, but having empty classes and empty interfaces feels wrong?To use a different persistence implementation (say Azure Table Storage), then a new
CustomerRepository
class would need to be created and would inherit aAzureTableStorageRepository<T>
. But this could lead to redundant code (multiple CustomerRepositories)? How would this effect mocking?Another implementation (say Azure Table Storage) has limitations on transnational support so the a AzureTableStorageUnitOfWork class wouldn't work in this context.
Are there any other issues with the way I have done this?
(I have taken most of my inspiration from this post)
I can say that this code is good enough for the first time try but it does have some places to improve.
Let's go through some of them.
1. Dependency injection (DI) and usage of IoC.
You use the simplest version of Service Locator pattern -
container
instance itself.I suggest you use 'constructor injection'. You can find more information here (ASP.NET MVC 4 Dependency Injection).
2. Unit of Work (UoW) scope.
I can't find lifestyle of
IUnitOfWork
andICustomerRepository
. I am not familiar with Unity but msdn says that TransientLifetimeManager is used by default. It means that you'll get a new instance every time when you resolve type.So, the following test fails:
And I expect that instance of
UnitOfWork
in your controller differs from the instance ofUnitOfWork
in your repository. Sometimes it may be resulted in bugs. But it is not highlighted in the ASP.NET MVC 4 Dependency Injection as an issue for Unity.In Castle Windsor PerWebRequest lifestyle is used to share the same instance of type within single http request.
It is common approach when
UnitOfWork
is a PerWebRequest component. CustomActionFilter
can be used in order to invokeCommit()
during invocation ofOnActionExecuted()
method.I would also rename the
SaveChanges()
method and call it simplyCommit
as it is called in the example and in the PoEAA.3.1. Dependencies on repositories.
If your repositories are going to be 'empty' it is not needed to create specific interfaces for them. It is possible to resolve
IRepository<Customer>
and have the following code in your controllerThere is a test that tests it.
But if you would like to have repositories that are 'layer of abstraction over the mapping layer where query construction code is concentrated.' (PoEAA, Repository)
3.2. Inheritance on EntityFrameworkRepository.
In this case I would create a simple
IRepository
and its implementation that knows how to work with EntityFramework infrastructure and can be easily replaced by another one (e.g.
AzureTableStorageRepository
).And now
CustomerRepository
can be a proxy and refer to it.