0

I am currently working in WinUI 3 .NET 6 application and needed to implemented a PredicateBuilder as I need to provide dynamic Lambda Conditions based on dynamic property names.


        private Expression<Func<MyModel, bool>> PrepareFilterExpression(MyModel model)
        {
            try
            {
                if (model is null)
                    return null;

                var predicate = PredicateBuilder.False<MyModel>();

                model.Conditions.ForEach(c =>
                {
                    var operatorType = c.ExpressionOperator;
                    if (!operatorType.HasValue)
                        operatorType = ExpressionOperatorType.Or;

                    var comparerType = c.Comparer;
                    if (!comparerType.HasValue)
                        comparerType = ExpressionComparerType.EqualTo;

                    string propertyNme = c.Key;

                    GetExpression(ref predicate, propertyNme, operatorType.Value, comparerType.Value, c.Value);
                });

                return predicate;
            }
            catch (Exception ex)
            {
                //
            }
            return null;
        }


        private void GetExpression(ref Expression<Func<MyModel, bool>> predicate,
                                   string propertyName,
                                   ExpressionOperatorType operatorType,
                                   ExpressionComparerType comparerType,
                                   string propertyValue)
        {
            switch (operatorType)
            {
                case ExpressionOperatorType.Or:
                case ExpressionOperatorType.None:
                    switch (comparerType)
                    {
                        //TODO: Need to start Accepting propertyName variable instead of static name of property.
                        case ExpressionComparerType.Contains: 
                             predicate = predicate.Or(w => Convert.ToString(w.GetType().GetProperty("Title").GetValue(w)).Contains(propertyValue, StringComparison.OrdinalIgnoreCase));
                            break;
                        case ExpressionComparerType.EqualTo:
                            predicate = predicate.Or(w => Convert.ToString(w.GetType().GetProperty("Title").GetValue(w)).Equals(propertyValue, StringComparison.OrdinalIgnoreCase));
                            break;
                    }
                    break;
                default:
                    break;
            }
        }

ExpressionOperatorType & ExpressionComparerType are the enums I created to prepare the predicate as per the need.

Issue is, predicate.Or() or predicate.And() methods work as expected when I provide the name of the property via static string. But if I pass the propertyName via variable, it fails to get the property value, ideally, Object.GetProperty("") fails to get the PropertyInfo.

This works fine:

predicate = predicate.Or(w => Convert.ToString(w.GetType().GetProperty("Title").GetValue(w)).Contains(propertyValue, StringComparison.OrdinalIgnoreCase));

This does not:

predicate = predicate.Or(w => Convert.ToString(w.GetType().GetProperty(propertyName).GetValue(w)).Contains(propertyValue, StringComparison.OrdinalIgnoreCase));

The PredicateBuilder I'm using (which I've used in one of my Xamarin project as well in netstandard 2.1 library) is as following:

//PredicateBuilder 

using myapp.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace myapp.Extensions
{
    /// <summary>    
    /// Enables the efficient, dynamic composition of query predicates.    
    /// </summary>    
    public static class PredicateBuilder
    {
        /// <summary>    
        /// Creates a predicate that evaluates to true.    
        /// </summary>    
        public static Expression<Func<T, bool>> True<T>() { return param => true; }

        /// <summary>    
        /// Creates a predicate that evaluates to false.    
        /// </summary>    
        public static Expression<Func<T, bool>> False<T>() { return param => false; }

        /// <summary>    
        /// Creates a predicate expression from the specified lambda expression.    
        /// </summary>    
        public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }

        /// <summary>    
        /// Combines the first predicate with the second using the logical "and".    
        /// </summary>    
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }

        /// <summary>    
        /// Compare the expression with property name and value as per the comparer.    
        /// </summary>    
        public static Expression<Func<T, bool>> CompareAnd<T>(this Expression<Func<T, bool>> first,
                                                              string propertyName,
                                                              string value,
                                                              ExpressionComparerType comparer)
        {
            switch (comparer)
            {
                case ExpressionComparerType.Contains:
                    return first.Compose(w => AppCommon.ToString(w.GetPropertyValue(propertyName)).Contains(value, StringComparison.OrdinalIgnoreCase), Expression.AndAlso);
                case ExpressionComparerType.EqualTo:
                    return first.Compose(w => AppCommon.ToString(w.GetPropertyValue(propertyName)).Equals(value, StringComparison.OrdinalIgnoreCase), Expression.AndAlso);
                case ExpressionComparerType.NotEqualTo:
                    return first.Compose(w => !AppCommon.ToString(w.GetPropertyValue(propertyName)).Equals(value, StringComparison.OrdinalIgnoreCase), Expression.AndAlso);
                case ExpressionComparerType.GreaterThan:
                    return first.Compose(w => AppCommon.ToDouble(w.GetPropertyValue(propertyName)) > AppCommon.ToDouble(value), Expression.AndAlso);
                case ExpressionComparerType.LessThan:
                    return first.Compose(w => AppCommon.ToDouble(w.GetPropertyValue(propertyName)) < AppCommon.ToDouble(value), Expression.AndAlso);
                case ExpressionComparerType.GreaterThanOrEqualTo:
                    return first.Compose(w => AppCommon.ToDouble(w.GetPropertyValue(propertyName)) >= AppCommon.ToDouble(value), Expression.AndAlso);
                case ExpressionComparerType.LessThanOrEqualTo:
                    return first.Compose(w => AppCommon.ToDouble(w.GetPropertyValue(propertyName)) <= AppCommon.ToDouble(value), Expression.AndAlso);
                default:
                    return default;
            }
        }

        /// <summary>    
        /// Compare the expression with property name and value as per the comparer.    
        /// </summary>    
        public static Expression<Func<T, bool>> CompareOr<T>(this Expression<Func<T, bool>> first,
                                                              string propertyName,
                                                              string value,
                                                              ExpressionComparerType comparer)
        {
            switch (comparer)
            {
                case ExpressionComparerType.Contains:
                    return first.Compose(w => AppCommon.ToString(w.GetPropertyValue(propertyName)).Contains(value, StringComparison.OrdinalIgnoreCase), Expression.OrElse);
                case ExpressionComparerType.EqualTo:
                    return first.Compose(w => AppCommon.ToString(w.GetPropertyValue(propertyName)).Equals(value, StringComparison.OrdinalIgnoreCase), Expression.OrElse);
                case ExpressionComparerType.NotEqualTo:
                    return first.Compose(w => !AppCommon.ToString(w.GetPropertyValue(propertyName)).Equals(value, StringComparison.OrdinalIgnoreCase), Expression.OrElse);
                case ExpressionComparerType.GreaterThan:
                    return first.Compose(w => AppCommon.ToDouble(w.GetPropertyValue(propertyName)) > AppCommon.ToDouble(value), Expression.OrElse);
                case ExpressionComparerType.LessThan:
                    return first.Compose(w => AppCommon.ToDouble(w.GetPropertyValue(propertyName)) < AppCommon.ToDouble(value), Expression.OrElse);
                case ExpressionComparerType.GreaterThanOrEqualTo:
                    return first.Compose(w => AppCommon.ToDouble(w.GetPropertyValue(propertyName)) >= AppCommon.ToDouble(value), Expression.OrElse);
                case ExpressionComparerType.LessThanOrEqualTo:
                    return first.Compose(w => AppCommon.ToDouble(w.GetPropertyValue(propertyName)) <= AppCommon.ToDouble(value), Expression.OrElse);
                default:
                    return default;
            }
        }

        /// <summary>    
        /// Combines the first predicate with the second using the logical "or".    
        /// </summary>    
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.OrElse);
        }

        /// <summary>    
        /// Negates the predicate.    
        /// </summary>    
        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
        {
            var negated = Expression.Not(expression.Body);
            return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
        }

        /// <summary>    
        /// Combines the first expression with the second using the specified merge function.    
        /// </summary>    
        static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // zip parameters (map from parameters of second to parameters of first)    
            var map = first.Parameters
                .Select((f, i) => new { f, s = second.Parameters[i] })
                .ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with the parameters in the first    
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // create a merged lambda expression with parameters from the first expression    
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        class ParameterRebinder : ExpressionVisitor
        {
            readonly Dictionary<ParameterExpression, ParameterExpression> map;

            ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }

            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }

            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;

                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }

                return base.VisitParameter(p);
            }
        }
    }
}

//Enums

    public enum ExpressionComparerType
    {
        None = 0,
        Contains = 1,
        EqualTo = 2,
        GreaterThan = 3,
        GreaterThanOrEqualTo = 4,
        LessThan = 5,
        LessThanOrEqualTo = 6,
        NotEqualTo = 7
    }

    public enum ExpressionOperatorType
    {
        None = 0,
        And = 1,
        Or = 2
    }

It seems to me the .net issue at first. However, I would appreciate the help to understand this better if I'm missing something here.

Thanks. :)

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
Sulay Joshi
  • 195
  • 2
  • 9
  • From what you have shown, I don't believe there is an error in your code. And it is extremely unlikely that it is an error in C#. So, most likely, you are not showing everything or are not interpreting everything correctly. Are you sure `c.Key` is set to `"Title"`? – NetMage Jun 22 '23 at 19:43
  • PS Don't use `List<>.ForEach` - it is never better than using a real `foreach` loop. – NetMage Jun 22 '23 at 19:47
  • @NetMage What Sulay has posted, I can assure you it is being set to "Title". Furthermore, we tried using real `foreach` loop as well already but result is same. – Mayur Paghdal Jun 23 '23 at 05:59
  • Perhaps try using LINQPad to debug and `Dump()` the `predicate`? – NetMage Jun 23 '23 at 18:21

0 Answers0