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)
Func<T>
dependency – Bittner