| I have decided to make my first post on my new blog about an old favorite. The repository pattern. While the repository pattern is in no way new it has evolved a lot over time. My implementation is no exception. I am a big fan of ORMs but not of tightly coupled dependencies. And so, my implementation aims to decouple my applications from being tied to a particular ORM, while still allowing me to take full advantage of the productivity ORMs offer. Interface – The key to loosely coupled architecture This decoupled implementation I am referring too is of course accomplished using an interface. This gives us the ability to change our implementation at any time without affecting the rest of the code base as long as our new implementation adheres to the interface. The key to good interface design it that it should be intuitive and encapsulate the functionality you are looking to publically expose to your application. public interface IRepository { void CommitChanges(); void Delete<T>(Expression<Func<T, bool>> expression) where T : class, new(); void Delete<T>(T item) where T : class, new(); void DeleteAll<T>() where T : class, new(); T Single<T>(Expression<Func<T, bool>> expression) where T : class, new(); T Single<T>(Expression<Func<T, bool>> expression, Expression<Func<T, object>> include) where T : class, new(); T Single<T>(Expression<Func<T, bool>> expression, params Expression<Func<T, object>>[] include) where T : class, new(); IQueryable<T> All<T>() where T : class, new(); IQueryable<T> All<T>(Expression<Func<T, object>> include) where T : class, new(); IQueryable<T> All<T>(params Expression<Func<T, object>>[] include) where T : class, new(); void Add<T>(T item) where T : class, new(); void Add<T>(IEnumerable<T> items) where T : class, new(); void Update<T>(T item) where T : class, new(); }
Implementation
Here is an example implementation of the above interface. This implementation is using an Entity Framework 4 generated data model. I could just have easily been a mock for your unit test, of an implementation using another ORM.
public class Repository : IRepository { ObjectContext _context; public Repository(ObjectContext context) { _context = context; } public void CommitChanges() { _context.SaveChanges(); } public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T: class, new() { var query = All<T>().Where(expression); foreach (var item in query) { Delete(item); } } public void Delete<T>(T item) where T: class, new() { _context.DeleteObject(item); } public void DeleteAll<T>() where T: class, new() { var query = All<T>(); foreach (var item in query) { Delete(item); } } public void Dispose() { _context.Dispose(); } public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T: class, new() { return All<T>().FirstOrDefault(expression); } public T Single<T>(Expression<Func<T, bool>> expression, Expression<Func<T, object>> include) where T : class, new() { return All<T>(include).FirstOrDefault(expression); } public T Single<T>(Expression<Func<T, bool>> expression, params Expression<Func<T, object>>[] include) where T : class, new() { return All<T>(include).FirstOrDefault(expression); } public IQueryable<T> All<T>() where T: class, new() { return _context.CreateQuery<T>(GetSetName<T>()).AsQueryable(); } public IQueryable<T> All<T>(Expression<Func<T, object>> include) where T : class, new() { return _context.CreateQuery<T>(GetSetName<T>()).Include<T>(include).AsQueryable(); } public IQueryable<T> All<T>(params Expression<Func<T, object>>[] include) where T : class, new() { return _context.CreateQuery<T>(GetSetName<T>()).Include<T>(include).AsQueryable(); } public void Add<T>(T item) where T: class, new() { _context.AddObject(GetSetName<T>(),item); } public void Add<T>(IEnumerable<T> items) where T: class, new() { foreach (var item in items) { Add(item); } } public void Update<T>(T item) where T: class, new() { throw new NotImplementedException("Not Required"); } string GetSetName<T>() { var entitySetProperty = _context.GetType().GetProperties() .Single(p => p.PropertyType.IsGenericType && typeof(IQueryable<>) .MakeGenericType(typeof(T)).IsAssignableFrom(p.PropertyType));
return entitySetProperty.Name; } }
Include Helper Extensions
Entity Frameworks Include extension method uses strings which I am not a big fan of so I wrote these helpers that enable me to use expressions instead to specify the child object I want to include in my results. This may seem trivial but stop and think. Over time all of those string are compounding liabilities that are slowly but surely degrading the maintainability of your application. Down the road long if someone changes the name of a type you will have runtime error instead of compile time errors when you use strings. This is why my interface used expression to define includes instead of strings. If the name of a type is changed this implementation will ensure you are notified of the errors at compile time.
public static class IncludeExtensions { public static ObjectQuery<T> Include<T>(this ObjectQuery<T> query, Expression<Func<T, object>> path) { // Retrieve member path: List<PropertyInfo> members = new List<PropertyInfo>(); CollectRelationalMembers(path, members); // Build string path: StringBuilder sb = new StringBuilder(); string separator = ""; foreach (MemberInfo member in members) { sb.Append(separator); sb.Append(member.Name); separator = "."; } return query.Include(sb.ToString()); } public static ObjectQuery<T> Include<T>(this ObjectQuery<T> query, params Expression<Func<T, object>>[] paths) { foreach (var path in paths) { // Retrieve member path: List<PropertyInfo> members = new List<PropertyInfo>(); CollectRelationalMembers(path, members); // Build string path: StringBuilder sb = new StringBuilder(); string separator = ""; foreach (MemberInfo member in members) { sb.Append(separator); sb.Append(member.Name); separator = "."; } query.Include(sb.ToString()); } return query; } private static void CollectRelationalMembers(Expression exp, IList<PropertyInfo> members) { if (exp.NodeType == ExpressionType.Lambda) { // At root, explore body: CollectRelationalMembers(((LambdaExpression)exp).Body, members); } else if (exp.NodeType == ExpressionType.MemberAccess) { MemberExpression mexp = (MemberExpression)exp; CollectRelationalMembers(mexp.Expression, members); members.Add((PropertyInfo)mexp.Member); } else if (exp.NodeType == ExpressionType.Call) { MethodCallExpression cexp = (MethodCallExpression)exp; if (cexp.Method.IsStatic == false) throw new InvalidOperationException("Invalid type of expression."); foreach (var arg in cexp.Arguments) CollectRelationalMembers(arg, members); } else if (exp.NodeType == ExpressionType.Parameter) { // Reached the toplevel: return; } else { throw new InvalidOperationException("Invalid type of expression."); } } }
Conclusions
This is one example of the repository pattern. There are many like it but this one is mine.
|