【问题标题】:How to filter by various variables?如何按各种变量过滤?
【发布时间】:2021-07-14 23:13:24
【问题描述】:

我一直在寻找解决问题的方法。所以,问题是如何过滤所有变量类型,如 int / double / decimal ...例如,我有 json 数据具有多个属性,例如 id、name、price、description ...我必须进行搜索适用于所有类型。现在,我只能查询 ("...") 字符串的字段。如何使 id/price(int / double / decimal) 成为可能?

private static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue)
{
    var parametr = Expression.Parameter(typeof(T));
    var property = Expression.Property(parametr, propertyName);
    var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });

    var objectContains = Expression.Constant(propertyValue, typeof(string));
    var objectEquals   = Expression.Equal(property, Expression.Constant(propertyValue));

    var constrantMethod = Expression.Call(property, containsMethod, objectContains);

    return Expression.Lambda<Func<T, bool>>(constrantMethod, parametr)
                .Or(Expression.Lambda<Func<T, bool>>(objectEquals, parametr));
}

public static IQueryable<T> ApplyFiltering<T>(this IQueryable<T> source, string propertyLabel, string propertyValue)
{
    if (string.IsNullOrEmpty(propertyLabel))
    {
       return source;
    }

    var propertyNames = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
                              .Where(property => property.PropertyType == typeof(string) && property.Name == propertyLabel)
                              .Select(property => property.Name);

    var predicate = PredicateBuilder.New<T>();

    foreach (var name in propertyNames)
    {
        predicate = predicate.Or(GetExpression<T>(name, propertyValue));
    }

    return source.Where(predicate);
}

【问题讨论】:

  • 请注意,您的 ApplyFiltering 没有意义,因为只有一个属性可以匹配 propertyLabel,因此针对 PropertyType 进行测试是毫无价值的,除非您巧妙地避免了类型不匹配错误,在这种情况下您可能不应该是微妙的。

标签: c# linq entity-framework-core


【解决方案1】:

我已将 API 更改为更通用,希望此实现能帮助您了解如何构建 ExpressionTree。

用法:

query = query.ApplyFiltering(new Dictionary<string, object>
{
    {"Id", "2"},
    {"StrValue", "st"},
    {"DoubleValue", "3.5"}
});

实施:

public static class FilterExtensions
{
    public static IQueryable<T> ApplyFiltering<T>(this IQueryable<T> source, IDictionary<string, object> values, bool isOr = true)
    {
        if (values.Count == 0)
            return source;

        var predicate = GeneratePredicate<T>(values, isOr, (p, v) => p.Contains(v));

        return source.Where(predicate);
    }

    public static Expression<Func<T, bool>> GeneratePredicate<T>(IDictionary<string, object> values, bool isOr, Expression<Func<string, string, bool>> stringPredicate)
    {
        var entityParam = Expression.Parameter(typeof(T), "e");

        Expression predicate = null;

        foreach (var pair in values)
        {
            var propPath = MakePropPath(entityParam, pair.Key);

            Expression propPredicate;

            if (pair.Value == null)
            {
                // Decide what to do with non-reference types
                if (!(propPath.Type.IsClass || propPath.Type.IsInterface))
                    continue;

                propPredicate = Expression.Equal(propPath, Expression.Constant(null, propPath.Type));
            }
            else
            {
                var convertType = propPath.Type;
                if (convertType.IsGenericType && convertType.GetGenericTypeDefinition() == typeof(Nullable<>))
                    convertType = convertType.GetGenericArguments()[0];

                var value = convertType == pair.Value.GetType()
                    ? pair.Value
                    : Convert.ChangeType(pair.Value, convertType);

                Expression valueExpression = Expression.Constant(value);

                if (stringPredicate != null && value is string)
                {
                    propPredicate = ExpressionReplacer.GetBody(stringPredicate, propPath, valueExpression);
                }
                else
                {
                    if (valueExpression.Type != propPath.Type)
                        valueExpression = Expression.Convert(valueExpression, propPath.Type);

                    propPredicate = Expression.Equal(propPath, valueExpression);
                }
            }   
            
            if (predicate == null)
                predicate = propPredicate;
            else
            {
                predicate = isOr ? Expression.OrElse(predicate, propPredicate) : Expression.AndAlso(predicate, propPredicate);
            }
        }

        if (predicate == null)
            predicate = Expression.Constant(true);

        return Expression.Lambda<Func<T, bool>>(predicate, entityParam);
    }

    static Expression MakePropPath(Expression objExpression, string path)
    {
        return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
    }

    class ExpressionReplacer : ExpressionVisitor
    {
        readonly IDictionary<Expression, Expression> _replaceMap;

        public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
        {
            _replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
        }

        public override Expression Visit(Expression node)
        {
            if (node != null && _replaceMap.TryGetValue(node, out var replacement))
                return replacement;
            return base.Visit(node);
        }

        public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
        {
            return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
        }

        public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
        {
            return new ExpressionReplacer(replaceMap).Visit(expr);
        }

        public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
        {
            if (lambda.Parameters.Count != toReplace.Length)
                throw new InvalidOperationException();

            return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
                .ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
        }
    }
}

【讨论】:

  • 最好使用Dictionary&lt;string, object&gt;
  • 我预测过滤器将来自 JSON。无论如何,变化很小。根据要求,我会更正。
  • @GuruStron,好吧,也许你是对的。更正为object
【解决方案2】:

我使用属性类型并转换字符串值以匹配等于 testing. I modified the code for non-strings to use ToStringon the property or value as needed soContainswould still work, assuming you wanted to filter by contain on non-strings. I assumed no property would be a collection class, so onlyString.Contains 支持。

public static class TestExt {
    static MethodInfo stringContainsMI = typeof(string).GetMethod("Contains", new[] { typeof(string) });

    private static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue) {
        // p
        var pParm = Expression.Parameter(typeof(T), "p");
        // p.{propertyName}
        var propertyExpr = Expression.Property(pParm, propertyName);
        var propType = propertyExpr.Type;

        Expression propValConst;
        if (propType == typeof(string))
            // {propertyValue}
            propValConst = Expression.Constant(propertyValue);
        else
            // {propertyValue as propType}
            propValConst = Expression.Constant(Convert.ChangeType(propertyValue, propType));

        // p.{PropertyName} == {propertyValue}
        var equalsExpr = Expression.Equal(propertyExpr, propValConst);

        Expression propValAsStringExpr = Expression.Constant(propertyValue);
        Expression propertyAsStringExpr;
        if (propType == typeof(string))
            // p.{propertyName}
            propertyAsStringExpr = propertyExpr;
        else {
            var propToStringMI = propType.GetMethod("ToString", Type.EmptyTypes);
            // p.{propertyName}.ToString()
            propertyAsStringExpr = Expression.Call(propertyExpr, propToStringMI);
        }

        // p.{propertyName}.Contains({propertyValue}) or p.{propertyName}.ToString().Contains({propertyValue})
        var containsExpr = Expression.Call(propertyAsStringExpr, stringContainsMI, propValAsStringExpr);
        //  (p.{PropertyName} == {propertyValue}) || p.{propertyName}.Contains({propertyValue})
        var lambdaBody = Expression.OrElse(equalsExpr, containsExpr);

        // p => (p.{PropertyName} == {propertyValue}) || p.{propertyName}.Contains({propertyValue})
        return Expression.Lambda<Func<T, bool>>(lambdaBody, pParm);
    }

    public static IQueryable<T> ApplyFiltering<T>(this IQueryable<T> source, string propertyLabel, string propertyValue) {
            var predicate = GetExpression<T>(propertyLabel, propertyValue);

            return source.Where(predicate);
    }
}

【讨论】:

  • 哦,这对我来说开始变得如此复杂 xD。谢谢你很配。但是,现在我应该如何传递参数进行查询。我有过滤器模型,它有 searchBy 和 searchValue attbs。使用我之前的变体,我进行了如下查询:?searchBy=Description&searchValue=600(使用 600 获取 desc,但我无法搜索其他类型)。如果我只想通过价格怎么办? [?searchBy=Price&searchValue=499 - 不显示任何内容](了解过滤器现在只应用一个参数值)
  • @A.Laketych 我想我会以与我所做的相反的方式更改代码 - 放回 propertyLabel 并删除类型上的循环,因为只能有一个匹配项。由于您的数据是以字符串形式出现的,因此您需要决定是将匹配值转换为属性类型还是将属性转换为字符串(这可能会导致分数出现问题)。
  • @A.Laketych 我更新了我的答案以再次采用propertyLabel 并将字符串值转换为匹配类型,而不是采用任何类型。
猜你喜欢
  • 1970-01-01
  • 2022-11-15
  • 2015-07-15
  • 1970-01-01
  • 2021-11-26
  • 1970-01-01
  • 1970-01-01
  • 2020-09-08
  • 1970-01-01
相关资源
最近更新 更多