65.9K
CodeProject is changing. Read more.
Home

Dynamically creating a predicate delegate

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5 votes)

May 30, 2014

CPOL

1 min read

viewsIcon

22597

downloadIcon

92

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.