Codementor Events

How to: Use Expression Trees to Build Dynamic Queries (C#)

Published Aug 18, 2019Last updated Jun 26, 2020

This topic describes how to use expression trees to create dynamic LINQ queries. Dynamic queries are useful when the specifics of a query are not known at compile time. For example, an application might provide a user interface that enables the end user to specify one or more predicates to filter the data. In order to use LINQ for querying, this kind of application must use expression trees to create the LINQ query at runtime.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-use-expression-trees-to-build-dynamic-queries

We create SwapVisitor and ExpressionHelper.

SwapVisitor allows you to replace the parameters of the concatenated expressions with the parameter of one of them.
All expressions must reference the same parameter object and that is the purpose of this class.

ExpressionHelper allows you to create all the necessary expressions and concatenate them with an and / or

Allowed expressions: Equals, NotEquals, Minor, MinorEquals, Mayor, MayorEquals, Like,Contains, Any



    public class SwapVisitor : ExpressionVisitor
    {
        private readonly Expression from, to;
        public SwapVisitor(Expression from, Expression to)
        {
            this.from = from;
            this.to = to;
        }
        public override Expression Visit(Expression node)
        {
            return node == from ? to : base.Visit(node);
        }
    }
    public static class ExpressionHelper
    {
        #region -- Public methods --
        public static Expression<Func<T, bool>> GetCriteriaWhere<T>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
        {
            string name = GetOperand<T>(e);
            return GetCriteriaWhere<T>(name, selectedOperator, fieldValue);
        }

        public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
        {
            string name = GetOperand<T>(e);
            return GetCriteriaWhere<T, T2>(name, selectedOperator, fieldValue);
        }

        public static Expression<Func<T, bool>> GetCriteriaWhere<T>(string fieldName, OperationExpression selectedOperator, object fieldValue)
        {


            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
            PropertyDescriptor prop = GetProperty(props, fieldName, true);

            var parameter = Expression.Parameter(typeof(T));
            var expressionParameter = GetMemberExpression<T>(parameter, fieldName);

            if (prop != null && fieldValue != null)
            {

                BinaryExpression body = null;

                switch (selectedOperator)
                {
                    case OperationExpression.Equals:
                        body = Expression.Equal(expressionParameter, Expression.Constant(fieldValue, prop.PropertyType));
                        return Expression.Lambda<Func<T, bool>>(body, parameter);
                    case OperationExpression.NotEquals:
                        body = Expression.NotEqual(expressionParameter, Expression.Constant(fieldValue, prop.PropertyType));
                        return Expression.Lambda<Func<T, bool>>(body, parameter);
                    case OperationExpression.Minor:
                        body = Expression.LessThan(expressionParameter, Expression.Constant(fieldValue, prop.PropertyType));
                        return Expression.Lambda<Func<T, bool>>(body, parameter);
                    case OperationExpression.MinorEquals:
                        body = Expression.LessThanOrEqual(expressionParameter, Expression.Constant(fieldValue, prop.PropertyType));
                        return Expression.Lambda<Func<T, bool>>(body, parameter);
                    case OperationExpression.Mayor:
                        body = Expression.GreaterThan(expressionParameter, Expression.Constant(fieldValue, prop.PropertyType));
                        return Expression.Lambda<Func<T, bool>>(body, parameter);
                    case OperationExpression.MayorEquals:
                        body = Expression.GreaterThanOrEqual(expressionParameter, Expression.Constant(fieldValue, prop.PropertyType));
                        return Expression.Lambda<Func<T, bool>>(body, parameter);
                    case OperationExpression.Like:
                        MethodInfo contains = typeof(string).GetMethod("Contains");
                        var bodyLike = Expression.Call(expressionParameter, contains, Expression.Constant(fieldValue, prop.PropertyType));
                        return Expression.Lambda<Func<T, bool>>(bodyLike, parameter);
                    case OperationExpression.Contains:
                        return Contains<T>(fieldValue, parameter, expressionParameter);
                

                    default:
                        throw new Exception("Not implement Operation");
                }
            }
            else
            {
                Expression<Func<T, bool>> filter = x => true;
                return filter;
            }
        }

        public static Expression<Func<T, bool>> GetCriteriaWhere<T,T2>(string fieldName, OperationExpression selectedOperator, object fieldValue)
        {


            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
            PropertyDescriptor prop = GetProperty(props, fieldName, true);

            var parameter = Expression.Parameter(typeof(T));
            var expressionParameter = GetMemberExpression<T>(parameter, fieldName);
            
            if (prop != null && fieldValue != null)
            {
                switch (selectedOperator)
                {
                    case OperationExpression.Any:
                        return Any<T,T2>(fieldValue, parameter, expressionParameter);

                    default:
                        throw new Exception("Not implement Operation");
                }
            }
            else
            {
                Expression<Func<T, bool>> filter = x => true;
                return filter;
            }
        }

      

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> or)
        {
            if (expr == null) return or;
            return Expression.Lambda<Func<T, bool>>(Expression.OrElse(new SwapVisitor(expr.Parameters[0], or.Parameters[0]).Visit(expr.Body), or.Body), or.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> and)
        {
            if (expr == null) return and;
            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(new SwapVisitor(expr.Parameters[0], and.Parameters[0]).Visit(expr.Body), and.Body), and.Parameters);
        }

        #endregion
        #region -- Private methods --

        private static string GetOperand<T>(Expression<Func<T,object>> exp)
        {
            MemberExpression body = exp.Body as MemberExpression;

            if (body == null)
            {
                UnaryExpression ubody = (UnaryExpression)exp.Body;
                body = ubody.Operand as MemberExpression;
            }

            var operand = body.ToString();

            return operand.Substring(2);
            
        }
        
        private static MemberExpression GetMemberExpression<T>(ParameterExpression parameter, string propName)
        {
            if (string.IsNullOrEmpty(propName)) return null;
            var propertiesName = propName.Split('.');
            if (propertiesName.Count() == 2)
                return Expression.Property(Expression.Property(parameter, propertiesName[0]), propertiesName[1]);
            return Expression.Property(parameter, propName);
        }

        private static Expression<Func<T, bool>> Contains<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression)
        {
            var list = (List<long>)fieldValue;

            if (list == null || list.Count == 0) return x => true;

            MethodInfo containsInList = typeof(List<long>).GetMethod("Contains", new Type[] { typeof(long) });
            var bodyContains = Expression.Call(Expression.Constant(fieldValue), containsInList, memberExpression);

            return Expression.Lambda<Func<T, bool>>(bodyContains, parameterExpression);
        }


        private static Expression<Func<T, bool>> Any<T, T2>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression)
        {
            var lambda = (Expression<Func<T2, bool>>) fieldValue;
            MethodInfo anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
            .First(m => m.Name == "Any" && m.GetParameters().Count() == 2).MakeGenericMethod(typeof(T2)); 

            var body = Expression.Call(anyMethod, memberExpression, lambda);          

            return Expression.Lambda<Func<T, bool>>(body, parameterExpression);
        }

        private static PropertyDescriptor GetProperty(PropertyDescriptorCollection props, string fieldName, bool ignoreCase)
        {           
            if (!fieldName.Contains('.'))
                return props.Find(fieldName, ignoreCase);

            var fieldNameProperty = fieldName.Split('.');
            return props.Find(fieldNameProperty[0], ignoreCase).GetChildProperties().Find(fieldNameProperty[1], ignoreCase);

        }
        #endregion
    }

    public enum OperationExpression
    {
        Equals,
        NotEquals,
        Minor,
        MinorEquals,
        Mayor,
        MayorEquals,
        Like,
        Contains,
        Any
    }



Use of the ExpressionHelper class

   public override Expression<Func<T, bool>> GetExpressionToFilter(TFilter filter)
        {

            Expression<Func<T1, bool>> lambdaOriginId = x => x.OriginId == filter.OriginId;
            Expression<Func<T1, bool>> lambdaDestinationId = x => x.DestinationId == filter.DestinationId;

            var c = ExpressionHelper.GetCriteriaWhere<T>(a => a.Name, OperationExpression.Like, filter.Name);
            c = c.And(ExpressionHelper.GetCriteriaWhere<T>(a => a.Description, OperationExpression.Like, filter.Description));
            c = c.And(ExpressionHelper.GetCriteriaWhere<T>(a => a.Active, OperationExpression.Equals, filter.Active));

            if (filter.OriginId.HasValue)
                c = c.And(ExpressionHelper.GetCriteriaWhere<T, T1>(a => a.List, OperationExpression.Any, lambdaOriginId));
            if (filter.DestinationId.HasValue)
                c = c.And(ExpressionHelper.GetCriteriaWhere<T, T1>(a => a.List, OperationExpression.Any, lambdaDestinationId));
            return c;
        }

Using of the ExpressionHelper class with the following classes

 public class T 
    {
       
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Column("id")]
        public long Id { get; set; }
        
        [Column("name")]
        [Index(IsUnique = true)]
        [Required]       
        public string Name { get; set; }

        
        [Column("description")]
        [Required]       
        public string Description { get; set; }

        public virtual List<T1> List { get; set; }
        
        [Column("active")]
        [Required]       
        public bool Active { get; set; }


    }
    
     public class T1 
    {
       
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Column("id")]
        public long Id { get; set; }

        [Column("ori_id")]        
        [Required]        
        public long OriginId { get; set; }
        
    [Column("des_id")]        
        [Required]        
        public long DestinationId { get; set; }
    }
  
  
   public class TFilter 
    {
        
    public string Name { get; set; }
    
    public string Description { get; set; }

        public bool? Active { get; set; }        

        public long? OriginId { get; set; }

        public long? DestinationId { get; set; }
    }

Thanks for reading! Hope it helps.

Discover and read more posts from Julián Andrés D'Ambrosio
get started
post commentsBe the first to share your opinion
Ha Duy Nguyen
3 years ago

Thank you for your article, it’s exactly what I’m looking for. Just one question: take T vs T1 classes for instance, and using OperationExpression.Any, what if I want to apply filter on only the latest T1 (by DateCreated or Id) , then how should I do it ?

For example: I want to select T entities that has the latest T1 with DestinationId == filter.DestinationId

Julián Andrés D'Ambrosio
3 years ago

Hi ! T is the main class, and T1 is the list class. For example…

public class T {
private List<T1> List { get; set; }
}

public class T1{

private DateTime DateCreated { get; set; }
}

public class Filter {
private DateTime? DateCreated { get; set; } //It’s nulleable.
}

Then you can use…

Expression<Func<T1, bool>> lambdaDateCreated = x => x.DateCreated== filter.DateCreated;
var c = x=> true; //use others filters if it’s necessary.
if (filter.DateCreated.HasValue) {
c = c.And(ExpressionHelper.GetCriteriaWhere<T, T1>(a => a.List, OperationExpression.Any, lambdaDateCreated ));
}
return c;

I hope this can help you! Let me know! Regards

Himanish
4 years ago

very good article, I have a doubt could you please explain more on how are you
using the helper class. please provide definition of the objects ChangeOfCategoryMotive and ChangeOfCategoryMotiveFilter

Julián Andrés D'Ambrosio
4 years ago

Hi Himanish, thanks you ! I have updated the article for you to understand it better. Delete the ChangeOfCategoryMotive and ChangeOfCategoryMotiveFilter entities and add the definitions for T, T1 and TFilter. They are the 3 classes necessary for this example. I hope this can help you! Let me know! Regards

Show more replies