Introduction
The aim of this article is to introduce a way of useful realization of repository pattern based on Entity Framework. If you want, you can easily create realization of RepositoryUnit
for Linq2Sql and write unit tests. Probably, I will do it in further articles.
Background
Repository pattern provides uniform access to data and it is middle tier between data source and data consumers. It allows abstract data source from application's business logic. In realization of this concept, we can encounter several problems:
- Application usually works with objects (classes) for which there are defined CRUD (create, read, update, delete) operations in abstraction interface. Therefore even in case of small quantity of classes repository's interface will consist of the same kit of CRUD functions for each class. We certainly can write separate interface for each class and put them together in general repository, but in this case we will have a pile of similar files that is not good at all. Generic types of C# help us to avoid this problem.
- To make CRUD operations, we should be able to identify objects. Usually this problem is solved by inheritance from the class that has ID field, but in this case we have to edit files generated by EF, what usually doesn't bring a lot of pleasure.
Let's finish with difficulties and go to benefits of proposed solution.
Features
- Strong typization.
- CRUD-operations without inheritance from base class. Object's ID is got through reflection.
- Security - you cannot get
IQueryable
directly. Repository returns only lists and their derivatives. If you want to return Queryable
you can do it in inherited class. - Lambda-expression sorting with saving ordering inside repository instance. You can define sorting only one time when you create your repository and forget about it. Also you can set the default filtering, that will define data your repository will work with (for example, ignore objects with
IsDeleted=true
). - CRUD-events. Often, it is easier to use event, than inherit and override method.
- Class's properties and methods encapsulation in one property of general repository.
IRepositoryUnit
It is simple and nothing to write. This interface simply does what was written above.
public interface IRepositoryUnit<T, TId>
{
T Create(T objectToCreate);
void Delete(TId id);
T Edit(T objectToUpdate);
T Get(TId id);
T Find(Expression<Func<T, bool>> predicate);
IEnumerable<T> List();
PaginatedList<T> List(int? page, int pageSize);
PaginatedList<T> FindList(int? page, int pageSize,
Expression<Func<T, bool>> predicate);
IEnumerable<T> FindList(Expression<Func<T, bool>> predicate);
void OrderBy<TKey>(Expression<Func<T, TKey>> key);
void ThenBy<TKey>(Expression<Func<T, TKey>> key);
void OrderByDesc<TKey>(Expression<Func<T, TKey>> key);
void ThenByDesc<TKey>(Expression<Func<T, TKey>> key);
Expression<Func<T, bool>> WherePredicate { get; set; }
event Action<T> Deleted;
event Func<T, bool> Creating;
event Func<T, T, bool> Editing;
}
EntityRepositoryUnit
Because realization for EF is quite big, we will examine only the most interesting moments.
Constructor gets ObjectContext
and, optionally, includePaths
. Second parameter defines tables which should be included in the query. Name of necessary data set is detected by method FetchEntitySetName
(thank Manuel Felicio) in object creation routine.
Method Query
is the most important. Every request processing (except Get
) begins with Query
. It prepares query, applies filtering and ordering and returns convenient IQueryable
object.
public class EntityRepositoryUnit<T, TId> : IRepositoryUnit<T, TId>
where T : EntityObject
{
protected ObjectContext ObjectContext { get; private set; }
protected string EntitySetName { get; private set; }
protected string[] IncludePaths { get; private set; }
private Type orderByType;
private Expression orderByExpression;
private bool orderByDesc;
private List<Type> thenByType;
private List<Expression> thenByExpression;
private List<bool> thenByDesc;
public Expression<Func<T, bool>> WherePredicate { get; set; }
...
public EntityRepositoryUnit(ObjectContext context, string[] includePaths)
{
this.ObjectContext = context;
this.IncludePaths = includePaths;
FetchEntitySetName();
}
private void FetchEntitySetName()
{
var entitySetProperty =
this.ObjectContext.GetType().GetProperties()
.Single(p => p.PropertyType.IsGenericType && typeof(IQueryable<>)
.MakeGenericType(typeof(T)).IsAssignableFrom(p.PropertyType));
this.EntitySetName = entitySetProperty.Name;
}
protected IQueryable<T> BuildQuery(ObjectQuery<T> q)
{
if (IncludePaths != null)
foreach (string path in IncludePaths)
q = q.Include(path);
if (WherePredicate != null)
return q.Where(WherePredicate);
else
return q;
}
protected IQueryable<T> ApplySorting(IQueryable<T> q)
{
if (orderByExpression != null)
{
var mc = Expression.Call(
typeof(Queryable),
orderByDesc ? "OrderByDescending" : "OrderBy",
new Type[] { q.ElementType, orderByType },
q.Expression,
orderByExpression);
for (int i = 0; i < thenByExpression.Count; i++)
{
mc = Expression.Call(
typeof(Queryable),
thenByDesc[i] ? "ThenByDescending" : "ThenBy",
new Type[] { q.ElementType, thenByType[i] },
mc,
thenByExpression[i]);
}
return q.Provider.CreateQuery(mc) as IQueryable<T>;
}
return q;
}
protected IQueryable<T> Query(Expression<Func<T, bool>> predicate)
{
var entitySet = String.Format("[{0}]", this.EntitySetName);
ObjectQuery<T> baseQuery = this.ObjectContext.CreateQuery<T>(entitySet);
var q = BuildQuery(baseQuery);
if(predicate!=null)
q = q.Where(predicate);
q = ApplySorting(q);
return q;
}
#region IRepositoryUnit<T,TId> Members
...
public T Get(TId id)
{
string queryString = string.Format(@"SELECT VALUE x FROM
[{0}] as x WHERE x.Id = @Id", EntitySetName);
var q = ObjectContext.CreateQuery<T>(queryString, new ObjectParameter("Id", id));
return BuildQuery(q).FirstOrDefault();
}
...
public PaginatedList<T> FindList
(int? page, int pageSize, Expression<Func<T, bool>> predicate)
{
return new PaginatedList<T>(Query(predicate), page, pageSize);
}
...
#endregion
}
PaginatedList
It is a subsidiary class, it allows to get data for display on a specified page. It's code you can view in the downloaded project.
Using the Code
It is very easy. General repository of your project will contain properties - objects realized interface IRepositoryUnit
of type you need. Only very intricate methods operating complex logic on data store level will be described as methods of your general repository.
For example:
public interface IRepository
{
IRepositoryUnit<News, int> News { get; }
IRepositoryUnit<Message, Guid> Messages { get; }
IRepositoryUnit<Topic, Guid> Topics { get; }
IRepositoryUnit<PhotoType, int> PhotoTypes { get; }
IRepositoryUnit<Photo, Guid> Photos { get; }
IEnumerable<Message> VeryIntricateMethod1();
IEnumerable<Photo> VeryIntricateMethod1();
}
Such repository lets easily work with five entities and contains two complex methods that are not reasonable to make on RepositoryUnit
's level.
EF-realization of that interface cannot fail to please us. It is simple and clear:
public class EntityRepository : IRepository
{
private YourEntities entities = new YourEntities();
public EntityRepository()
{
News = new EntityRepositoryUnit<News, int>(entities);
News.OrderByDesc(x => x.Date);
PhotoTypes = new EntityRepositoryUnit<PhotoType, int>(entities);
PhotoTypes.OrderBy(x => x.Name);
Messages = new EntityRepositoryUnit<Message, Guid>(entities);
Messages.WherePredicate = x => x.IsDeleted = false;
Messages.OrderBy(x => x.Date);
Topics = new EntityRepositoryUnit<Topic, Guid>
(entities, new string[] { "Message" });
Topics.WherePredicate = x => x.IsDeleted = false;
Topics.OrderBy(x => x.Date);
Photos = new EntityRepositoryUnit<Photo,
Guid>(entities, new string[] { "PhotoType" });
Photos.OrderBy(x => x.PhotoType.Id);
Photos.ThenBy(x => x.Date);
Photos.Deleted += new Action<Photo>(s => s.DeleteFiles());
}
#region IRepository Members
public IRepositoryUnit<News, int> News { get; private set; }
public IRepositoryUnit<Message, Guid> Messages { get; private set; }
public IRepositoryUnit<Topic, Guid> Topics { get; private set; }
public IRepositoryUnit<PhotoType, int> PhotoTypes { get; private set; }
public IRepositoryUnit<Photo, Guid> Photos { get; private set; }
public IEnumerable<Message> VeryIntricateMethod1()
{
throw new NotImplementedException();
}
IEnumerable<Photo> IRepository.VeryIntricateMethod1()
{
throw new NotImplementedException();
}
}
Pay attention, sorting for each repository is defined - otherwise you will get an error in PaginatedList
constructor. Also you should remember that each Entity
object must have ID
property (see method EntityRepositoryUnit.Get
). Notice how files with photos are beautifully deleted at Photo
object's removal from the repository.
I will not give examples of calling repository's functions from business-logic, because they are obvious enough. You will find a simple MVC example in the downloaded archive. If you have questions - you are welcome to ask them in the comments sections below.
History
- 26th March, 2010: Initial version