Generic Repository Search function with Expression Trees

2 min read

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.