Generic Repository Search function with Expression Trees
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.
- How to: Use Expression Trees to Build Dynamic Queries
- How do I dynamically create an Expression<Func<MyClass, bool>> predicate?
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.