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. :)