Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

RepositoryUnit: Useful Generic Repository

4.89/5 (8 votes)
27 Mar 2010CPOL3 min read 1   1.1K  
The aim of this article is to introduce a way of useful realization of repository pattern based on Entity Framework.

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.

C#
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.

C#
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();
    }

    //looks for an IQueryable<TEntity> property in the ObjectContext
    //and gets its name to be used in other methods
    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:

C#
 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:

C#
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()
     {
         //your intricate logic here
         throw new NotImplementedException();
     }

     IEnumerable<Photo> IRepository.VeryIntricateMethod1()
     {
         //your intricate logic here
         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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)