Pages

Wednesday, June 27, 2012

Using Pete Montgomery’s Universal Predicate Builder with a generic repository

I gave a presentation the other day that included a generic repository class that I created and use in many of my Entity Framework projects. I like the implementation because it is flexible and easy to extend. I've created a blog post about the RepositoryBase class here:

A Generic Repository for the Entity Framework

After the presentation I was asked how I utilized the repository's Select methods. In this case, the Select Method is overridden a few times to match the select pattern used by ASP.Net ObjectDataSources. Essentially, I can create a repository class that will map 1 to 1 to an ObjectDataSource to enable paging and sorting.
Here is the generic entity repository class from my other post (Pay attention to the Select methods):

public abstract class GenericEntityRepository<T>
where T : class
{
    #region Properties

    public BaseRepository Repository
    {
        get
        {
            return Activator.CreateInstance(BaseRepositoryType) as BaseRepository;
        }
    }

    public abstract Type BaseRepositoryType { get; }

    #endregion

    #region Methods

    public virtual void Delete(T entity)
    {
        ExecuteQuery(query =>
        {
            query.Delete<T>(entity);
            query.SaveChanges();
        });
    }

    internal virtual void ExecuteQuery(Action<IRepository> query)
    {
        if (query == null)
            throw new ArgumentException("The query argument can not be null.");

        using (var queryRepository = Repository)
        {
            query(queryRepository);
        }
    }

    public virtual void Insert(T entity)
    {
        ExecuteQuery(query =>
        {
            query.Insert<T>(entity);
            query.SaveChanges();
        });
    }

    public virtual IList<T> Select()
    {
        List<T> results = new List<T>();

        ExecuteQuery(query =>
        {
            results = query.Select<T>().ToList();
        });

        return results;
    }

    public virtual IList<T> Select(int rowNumber, int pageSize)
    {
        // There is a bug in the EF stack that does not allow you to call the Skip()
        // function without calling the OrderBy() function.
        string defaultSort = StaticMethods.CreateSortExpression<T>(string.Empty, string.Empty);

        List<T> results = null;

        ExecuteQuery(query =>
        {
            results = query.Select<T>().AsQueryable<T>().OrderBy(defaultSort).Skip(rowNumber).Take(pageSize).ToList();
        });

        return results;
    }

    public virtual IList<T> Select(string sortByColumnName, int rowNumber, int pageSize)
    {
        if (string.IsNullOrEmpty(sortByColumnName))
            return Select(rowNumber, pageSize);

        List<T> results = null;

        ExecuteQuery(query =>
        {
            results = query.Select<T>().OrderBy(sortByColumnName, false).Skip(rowNumber).Take(pageSize).ToList();
        });

        return results;
    }

    public virtual IList<T> Select(Expression<func<bool>> where)
    {
        if (where == null)
            return Select();

        List<T> results = new List<T>();

        ExecuteQuery(query =>
        {
            results = query.Select<T>().Where<T>(where).ToList();
        });

        return results;
    }

    public virtual IList<T> Select(Expression<func<bool>> where, int rowNumber, int pageSize)
    {
        if (where == null)
            return Select(rowNumber, pageSize);

        // There is a bug in the EF stack that does not allow you to call the Skip()
        // function without calling the OrderBy() function.
        string defaultSort = StaticMethods.CreateSortExpression<T>(string.Empty, string.Empty);

        List<T> results = null;

        ExecuteQuery(query =>
        {
            results = query.Select<T>().Where<T>(where).AsQueryable<T>().OrderBy(defaultSort).Skip(rowNumber).Take(pageSize).ToList();
        });

        return results;
    }

    public virtual IList<T> Select(Expression<func<bool>> where, string sortByColumnName, int rowNumber, int pageSize)
    {
        if (where == null)
            return Select(sortByColumnName, rowNumber, pageSize);

        if (string.IsNullOrEmpty(sortByColumnName))
            return Select(where, rowNumber, pageSize);

        List<T> results = null;

        ExecuteQuery(query =>
        {
            results = query.Select<T>().Where<T>(where).AsQueryable<T>().OrderBy(sortByColumnName, false).Skip(rowNumber).Take(pageSize).ToList();
        });

        return results;
    }

    public virtual T Select(object key)
    {
        if (key == null)
            throw new ArgumentNullException("key", "The key argument can not be null.");

        T entity = null;

        ExecuteQuery(query =>
        {
            entity = query.Select<T>(key);
        });

        return entity;
    }

    public virtual int RowCount()
    {
        int rowCount = -1;

        ExecuteQuery(query =>
        {
            rowCount = query.Select<T>().Count();
        });

        return rowCount;
    }

    public virtual int RowCount(Expression<func<bool>> where)
    {
        if (where == null)
            return RowCount();

        int rowCount = -1;

        ExecuteQuery(query =>
        {
            rowCount = query.Select<T>().Where<T>(where).Count();
        });

        return rowCount;
    }

    public virtual void Update(T entity)
    {
        ExecuteQuery(query =>
        {
            query.Update<T>(entity);
            query.SaveChanges();
        });
    }

    #endregion
}


Notice that in the repository class I have an overload of the Select method that accepts an expression (Expression<Func<T, bool>>). This method allows you to pass an expression as a parameter, and the expression will be used to filter your Select query. Here is where the Predicate Builder comes in. Technically, you can use any method you want to create your expression, but I happen to like Pete Montgomery's implementation found here:

http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/

As he mention's in his post, this Predicate Builder class doesn't require strange calls to AsExpandable(), which is required if you use the famous "Albahari" PredicateBuilder.

Armed with Pete's PredicateBuilder you can implement something like the following in a calling class:

var s1 = PredicateBuilder.True<status>();
var s2 = PredicateBuilder.True<status>();
var whereClause = PredicateBuilder.True<status>();

s1 = s => s.Name == "Everett";
s2 = s => s.Active == true;
whereClause = s1.And(s2);

Status_Repository repository = new Status_Repository();
var statuses = repository.Select(whereClause);


This expression will filter the linq result set to only return Status objects where the Name property equals "Everett" and the Active property is true.

In my opinion this a great way to add flexibility to a repository solution and can help lead to better separation of concerns between application layers. This can also lead to more testable code when compared to other query filtering solutions like the Linq Dynamic library.

I'd love to hear feedback on this solution as well as other uses that you have found for any of the PredicateBuilder solutions out there!

No comments:

Post a Comment