none
Build dynamic filter expression RRS feed

  • Question

  • Hi!

    Could you help please,

    I want to build lamda for filtering list of objects. I want to have builder that will accept list of fields and values and return lambda to use later to filter the list.

    I've done the following:

     public class SurveyListFilterBuilder
        {
            public static Func<SurveysQueryResultItem, bool> Build(string[] searchField, string[] searchFieldValue, LogicalOperation operation)
            {
                Expression resultExpression = Expression.Constant(true);

                for (var i = 0; i < searchField.Length; i++)
                {
                    var field = searchField[i];
                    var fieldFilterExpression = GetFieldFilterExpression(field, searchFieldValue[i]);

                    if (operation == LogicalOperation.And)
                        resultExpression = Expression.And(resultExpression, fieldFilterExpression);
                    else if (operation == LogicalOperation.Or)
                        resultExpression = Expression.And(resultExpression, fieldFilterExpression);
                }

                return Expression.Lambda<Func<SurveysQueryResultItem, bool>>(resultExpression).Compile();
            }

            private static Expression<Func<MyClass, bool>> GetFieldFilterExpression(string field, string fieldValue)
            {
                switch (field)
                {
                    case "name":
                        return x => x.Name.Contains(fieldValue);
                    case "description":
                        return x => x.Name.Contains(fieldValue);
                        default:
                            throw new NotSupportedException();
                }
            }
        }

    But it doesn't work because it seems I apply ADD operator bool expression and expression of Func:

    The binary operator And is not defined for the types 'System.Boolean' and 'System.Func`2[ConsoleApp1.MyClass,System.Boolean]'.'

    The question is how can I fix it and achieve correct result?



    Alexander

    Sunday, November 18, 2018 10:42 AM

Answers

  • Hi,

    try this:

            public class SurveyListFilterBuilder
            {
                public static Func<MyClass, bool> Build(string[] searchField, string[] searchFieldValue, LogicalOperation operation)
                {
                    Expression resultExpression = Expression.Constant(true);
                    var paramExpr = Expression.Parameter(typeof(MyClass));
    
                    for (var i = 0; i < searchField.Length; i++)
                    {
                        var field = searchField[i];
                        var fieldFilterExpression = GetFieldFilterExpression(field, searchFieldValue[i]);
                        if (operation == LogicalOperation.And)
                            resultExpression = Expression.And(resultExpression, Expression.Invoke(fieldFilterExpression, paramExpr));
                        else if (operation == LogicalOperation.Or)
                            resultExpression = Expression.And(resultExpression, Expression.Invoke(fieldFilterExpression, paramExpr));
                    }
    
                    return Expression.Lambda<Func<MyClass, bool>>(resultExpression, paramExpr).Compile();
                }
    
                private static Expression<Func<MyClass, bool>> GetFieldFilterExpression(string field, string fieldValue)
                {
                    switch (field)
                    {
                        case "name":
                            return x => x.Name.Contains(fieldValue);
                        case "description":
                            return x => x.Description.Contains(fieldValue);
                        default:
                            throw new NotSupportedException();
                    }
                }
            }

    Greetings, Chris

    • Proposed as answer by Stanly Fan Tuesday, November 20, 2018 1:27 AM
    • Marked as answer by AlexanderZh Tuesday, November 20, 2018 7:27 AM
    Sunday, November 18, 2018 9:48 PM

All replies

  • Hi,

                    case "name":
                        return x => x.Name.Contains(fieldValue);
                    case "description":
                        return x => x.Description.Contains(fieldValue);
                        default:
                            throw new NotSupportedException();
    Should it not be like this?

    At the moment it's the same result for both cases...

    Sunday, November 18, 2018 12:16 PM
  • Can you show us MyClass, SurveysQueryResultItem and maybe some test data for searchFields and searchFieldValues. Without that I can not really test it...

    ...

    Sunday, November 18, 2018 1:14 PM
  • Sure, 

    public class MyClass
        {
            public string Name { get; set; }
            public string Description { get; set; }
        }

        public enum LogicalOperation
        {
            And,
            Or
        }

     public class FilterBuilder
        {
            public static Func<MyClass, bool> Build(string[] searchField, string[] searchFieldValue, LogicalOperation operation)
            {
                Expression resultExpression =  Expression.Constant(true);

                for (var i = 0; i < searchField.Length; i++)
                {
                    var field = searchField[i];
                    var fieldFilterExpression = GetFieldFilterExpression(field, searchFieldValue[i]);

                    if (operation == LogicalOperation.And)
                        resultExpression = Expression.AndAlso(resultExpression, fieldFilterExpression);
                    else if (operation == LogicalOperation.Or)
                        resultExpression = Expression.Or(resultExpression, fieldFilterExpression);              
                }

                var expressionParameter = Expression.Parameter(typeof(MyClass));
                var ex = Expression.Lambda(resultExpression, expressionParameter).Compile();
                return null;
            }

            private static Expression<Func<MyClass, bool>> GetFieldFilterExpression(string field, string fieldValue)
            {
                switch (field)
                {
                    case "name":
                        return x => x.Name.Contains(fieldValue);
                    case "Description":
                        return x => x.Description.Contains(fieldValue);                
                    default:
                        throw new NotSupportedException();
                }
            }
        }

    Usage:

    class Program
        {
            static void Main(string[] args)
            {
                var filter = FilterBuilder.Build(new[] {"name", "description"}, new[] {"xx", "123"}, LogicalOperation.And);

                var list = new List<MyClass>()
                {
                    new MyClass() {Name = "name xxx", Description = "description 123"}
                };

                var result = list.Where(filter);
            }
        }


    Alexander

    Sunday, November 18, 2018 9:11 PM
  • Hi,

    try this:

            public class SurveyListFilterBuilder
            {
                public static Func<MyClass, bool> Build(string[] searchField, string[] searchFieldValue, LogicalOperation operation)
                {
                    Expression resultExpression = Expression.Constant(true);
                    var paramExpr = Expression.Parameter(typeof(MyClass));
    
                    for (var i = 0; i < searchField.Length; i++)
                    {
                        var field = searchField[i];
                        var fieldFilterExpression = GetFieldFilterExpression(field, searchFieldValue[i]);
                        if (operation == LogicalOperation.And)
                            resultExpression = Expression.And(resultExpression, Expression.Invoke(fieldFilterExpression, paramExpr));
                        else if (operation == LogicalOperation.Or)
                            resultExpression = Expression.And(resultExpression, Expression.Invoke(fieldFilterExpression, paramExpr));
                    }
    
                    return Expression.Lambda<Func<MyClass, bool>>(resultExpression, paramExpr).Compile();
                }
    
                private static Expression<Func<MyClass, bool>> GetFieldFilterExpression(string field, string fieldValue)
                {
                    switch (field)
                    {
                        case "name":
                            return x => x.Name.Contains(fieldValue);
                        case "description":
                            return x => x.Description.Contains(fieldValue);
                        default:
                            throw new NotSupportedException();
                    }
                }
            }

    Greetings, Chris

    • Proposed as answer by Stanly Fan Tuesday, November 20, 2018 1:27 AM
    • Marked as answer by AlexanderZh Tuesday, November 20, 2018 7:27 AM
    Sunday, November 18, 2018 9:48 PM
  • But there is still something wrong at the Or branch: You are still using And and you need to start with false or it's alway true and you can break if one result is true...

    Sunday, November 18, 2018 10:16 PM
  • Hi,

    thank you, your solution works! about 'OR' you are right, we should start with false, fixed that


    Alexander

    Tuesday, November 20, 2018 7:28 AM