Generic Repository Search function with Expression Trees

by John Nye

04 Mar
2013

Now available as a nuget package. Search for 'SearchExtensions' or run the following:

PM> Install-Package NinjaNye.SearchExtensions

Source code can be found here: https://github.com/ninjanye/searchextensions


Expression trees have been a bit of a magic box for me for a while so I decided to do something about it and learn a bit about them.

I work with entity framework quite a bit and in most cases I use a base repository to perform the common logic such as retrieving records by id, or retrieving all records. I decided to try and implement a generic string search method on my base repository.

I decided that I wanted the following syntax when calling my search functionality:

this.repository.Search(x => x.Name, searchTerm);

To break this down, the first parameter (x => x.Name) is a lamda expression that represents the string property I want to search within. The second parameter is there search text I want to match on.

After much trial and error, trawling stack overflow and swatting up on the msdn documentation, I finally came up with the following

public class Repository<T> : IRepository<T> 
    where T : class, IEntity
{
    /// <summary>
    /// Performs a search on the supplied string property
    /// </summary>
    /// <param name="stringProperty">Property to search upon</param>
    /// <param name="searchTerm">Search term</param>
    public virtual IQueryable<T> Search(Expression<Func<T, string>> stringProperty, string searchTerm)
    {
        var source = this.RetrieveAll();

        if (String.IsNullOrEmpty(searchTerm))
        {
            return source;
        }

        //The following is the query we are trying to reproduce
        //source.Where(x => T.[property] != null 
        //               && T.[property].Contains(searchTerm)

        //Create expression to represent T.[property] != null
        var isNotNullExpression = Expression.NotEqual(stringProperty.Body, Expression.Constant(null));

        //Create expression to represent T.[property].Contains(searchTerm)
        var searchTermExpression = Expression.Constant(searchTerm);
        var checkContainsExpression = Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);

        //Join not null and contains expressions
        var notNullAndContainsExpression = Expression.AndAlso(isNotNullExpression, checkContainsExpression);

        //Build final expression
        var methodCallExpression = Expression.Call(typeof (Queryable), 
                                                   "Where", 
                                                   new Type[] {source.ElementType}, 
                                                   source.Expression, 
                                                   Expression.Lambda<Func<Club, bool>>(notNullAndContainsExpression, stringProperty.Parameters));

        return source.Provider.CreateQuery<T>(methodCallExpression);
    }

    public IDataContext DataContext { get; private set; }

    public Repository(IDataContext dataContext)
    {
        this.DataContext = dataContext;
    }

    /// <summary>
    /// Retrieve all records from context for a given type
    /// </summary>
    /// <returns></returns>
    public virtual IQueryable<T> RetrieveAll()
    {
        return this.DataContext.Set<T>();
    }
}

Thanks in particular to the following articles for helping me learn a bit about the magic box.

This task is not complete, I am still yet to look at the sql it produces when hooked up to my sql db. I am also yet to look at it's performance but that will be the subject of a later post where I will hopefully make some tweaks and enhancements.

Any questions or pointers, please add a comment and I'll do my best to get back to you.

Comments 0 * Be the first to comment!

Leave a message...

24 Sep
2017