Introduction
Recently, I worked on a task that involved creating a small search method. The application this was for uses an ORM and LINQ to query the database. I decided to look into building an Expression tree, After building my POC that proved building a dynamic predicate worked well, I completed my task. I decided to upload my POC. If I can help the next person with a similar project, that is great. For some, this may be very elementary, for others, a great kick start.
Using the code
The code uses a generic list of User class (entity/model) that is used to search, and a Search class where the dynamic Expression tree is built and the predicate created. Within the upload, I created a console app that brings this all together. Below are the contents of the Search used to build the predicate and the User class to search against within a list. I have also uploaded my POC.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
namespace MyPredicateBuilder
{
#region Search Class
public class Search
{
private List<User> _users;
public Search(List<User> users)
{
_users = users;
}
public List<User> Results { get; private set; }
public List<User> For(string firstName = "", string lastName = "", string email = "", int? userId = null)
{
var result = new List<User>();
var li = Expression.Parameter(typeof(User), "User");
Expression where = null;
if (!String.IsNullOrEmpty(firstName))
{
AndAlso(NameSearch(li, "First", firstName), ref where);
}
if (!String.IsNullOrEmpty(lastName))
{
AndAlso(NameSearch(li, "Last", lastName), ref where);
}
if (!String.IsNullOrEmpty(email))
{
AndAlso(EmailSearch(li, email), ref where);
}
if (userId.HasValue)
{
AndAlso(UserIdSearch(li, userId.Value), ref where);
}
if (where == null)
{
return _users;
}
var predicate = Expression.Lambda<Func<User, bool>>(where, new ParameterExpression[] { li }).Compile();
return _users.Where(predicate).ToList();
}
private void AndAlso(Expression equals, ref Expression op)
{
if (op == null)
{
op = equals;
}
else
{
op = Expression.AndAlso(op, equals);
}
}
private Expression NameSearch(Expression listOfNames, string propertyName, string value)
{
var nameObjInList = Expression.Property(listOfNames, "Name");
var nameProperty = Expression.Property(nameObjInList, propertyName);
var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var nameSearch = Expression.Constant(value, typeof(string));
var startWithCall = Expression.Call(nameProperty, startsWithMethod, new Expression[1] { nameSearch });
var right = Expression.Constant(true, typeof(bool));
return Expression.Equal(startWithCall, right);
}
private Expression EmailSearch(Expression listOfNames, string value)
{
Expression<Predicate<string>> emailPred = e => e.StartsWith(value);
var ep = Expression.Property(listOfNames, "Emails");
var isTrue = Expression.Constant(true, typeof(bool));
var existsMethod = typeof(List<string>).GetMethod("Exists", new[] { typeof(Predicate<string>) });
var existsCall = Expression.Call(ep, existsMethod, new Expression[1] { emailPred });
return Expression.Equal(existsCall, isTrue);
}
private Expression UserIdSearch(Expression listOfNames, int userId)
{
var userIdParam = Expression.Property(listOfNames, "UserId");
var userIdValue = Expression.Constant(userId, typeof(int));
return Expression.Equal(userIdParam, userIdValue);
}
}
#endregion
#region User Entity
public class UserName
{
public string First { get; set; }
public string Last { get; set; }
}
public class User
{
public User()
{
Emails = new List<String>();
Name = new UserName();
}
public int UserId { get; set; }
public UserName Name { get; set; }
public List<string> Emails { get; set; }
}
#endregion
}
Points of Interest
This was my first time using the Expression object and Reflection. After poking around more, I also saw a PredicateBuilder, url: http://www.albahari.com/nutshell/predicatebuilder.aspx. This definitely streamlines doing exactly the same thing. Call me crazy, but I like digging in the trenches (once in a while) especially when the solution I need is easy and straight forward, and I have the time to develop it.