Generic Repositories, Prepped for Injection
Posted by Tom on 2014-07-27 22:24
So I switched jobs recently, and am now working for a start-up. My remit when I first joined was to cut a bloody swath of refactoring through their evolved-prototype codebase.
First port of call was to change the entire service layer from statics to instantiated, interfaced classes, then have them dropped into our controllers via dependency injection. So far, so good. The next barrier to unit testing was the data access. It's provided by EF4 as an Object Context, and was created in the controller constructor, and then passed everywhere which needed it. Which is going to kibosh my plans.
So it's time to create some repositories! Writing data access code is excruciatingly dull, so let's see how far we can get with templating. No, not templating: generics. Sorry, I've been writing C++ again.
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
namespace Data
{
public interface IRepository<T>
{
IEnumerable<T> GetAll();
IEnumerable<T> Get(Expression<Func<T, bool>> predicate);
void Insert(T obj, bool save = true);
void Delete(T obj, bool save = true);
void Delete(Expression<Func<T, bool>> predicate, bool save = true);
void Save();
}
public abstract class Repository<T> : IRepository<T>, IDisposable where T : class
{
protected MyEntities DataContext { get; set; }
protected ObjectSet<T> Objects { get; set; }
public Repository()
{
DataContext = new MyEntities(ConfigurationManager.ConnectionStrings["MyEntities"].ConnectionString);
Objects = DataContext.CreateObjectSet<T>();
}
public IEnumerable<T> GetAll()
{
return Objects;
}
public IEnumerable<T> Get(Expression<Func<T, bool>> predicate)
{
return Objects.Where(predicate);
}
public void Insert(T obj, bool save = true)
{
Objects.AddObject(obj);
if (save)
Save();
}
public void Delete(T obj, bool save = true)
{
DataContext.DeleteObject(obj);
if (save)
Save();
}
public void Delete(Expression<Func<T, bool>> predicate, bool save = true)
{
foreach (T victim in Objects.Where(predicate))
Objects.DeleteObject(victim);
if (save)
Save();
}
public void Save()
{
DataContext.SaveChanges();
}
public void Dispose()
{
DataContext.Dispose();
}
}
}
There we go. That does most of the work for us. Now creating a repository for a particular object in your model is as simple as this:
using System;
using Model;
namespace Data
{
public interface IUserRepository : IRepository<User>
{
}
public class UserRepository : Repository<User>, IUserRepository
{
}
}
And there we have it. Insta-repositories! Just add concrete classes! You can still embellish the repository with more convenient methods as long as you add them to the model-object specific interface. And now we can mock the hell out of them and start to write isolated unit tests. Huzzah.
The only blemish on this beautiful Fowlerian landscape are many-to-many relationships, since EF gets antsy about us changing objects loaded from another context. In this instance we need to add hacks into the save method to reload the referenced objects from the same ObjectContext, apply our new values over the top of them, and then save them all back together. It's a bit manky, but I've yet to find a better way.