Dynamically creating a predicate delegate





5.00/5 (5 votes)
Created a POC that dynamically builds a predicate using Expression and Reflection.
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>(); //start building expression tree // Starting out with specifying the Entity/Model to be searched within the generic list. // In this case, we start with User list. 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); } } /// <summary> /// Search first/last user name /// </summary> /// <param name="listOfNames"></param> /// <param name="propertyName"></param> /// <param name="value"></param> /// <returns></returns> private Expression NameSearch(Expression listOfNames, string propertyName, string value) { // a. get expression for Name object within list var nameObjInList = Expression.Property(listOfNames, "Name"); // b. get expression of property found in Name object var nameProperty = Expression.Property(nameObjInList, propertyName); // c. get MethodInfo of the StartWith method found in the String object var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) }); // d. get expression that represents the name value to search var nameSearch = Expression.Constant(value, typeof(string)); // e. get the start with expression call var startWithCall = Expression.Call(nameProperty, startsWithMethod, new Expression[1] { nameSearch }); // f. get expression that represents the value for the StartWith return upon executing var right = Expression.Constant(true, typeof(bool)); // g. return final equals expression to help build predicate return Expression.Equal(startWithCall, right); } /// <summary> /// Search email within list of emails /// </summary> /// <param name="listOfNames"></param> /// <param name="value"></param> /// <returns></returns> private Expression EmailSearch(Expression listOfNames, string value) { //a. Create expression predicate. Expression<Predicate<string>> emailPred = e => e.StartsWith(value); //b. Create expression property for User propery Emails list var ep = Expression.Property(listOfNames, "Emails"); //c. Create constant value of return var isTrue = Expression.Constant(true, typeof(bool)); //d. Get methodinfo for string List<string>.Exists(..) method var existsMethod = typeof(List<string>).GetMethod("Exists", new[] { typeof(Predicate<string>) }); //e. Create expression call var existsCall = Expression.Call(ep, existsMethod, new Expression[1] { emailPred }); //f. return comparison expression 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.