【问题标题】:Expression tree to create dynamic Where clause throwing error related to params表达式树创建与参数相关的动态 Where 子句抛出错误
【发布时间】:2017-08-23 17:11:07
【问题描述】:

我有一个从客户端发送的条件列表列表。我需要获取这个列表并创建一个由 EntityFramework 执行的动态 where 子句。

每个条件都有一个运算符、一个属性和一个右侧值。

每个条件列表都需要用 AND 运算。

条件列表的每个列表都需要进行 OR 运算。

如果我们有

{  
   "ConditionLists":[  
      [  
         {  
            "LhsAttributeDefinition":{  
               "attribute":{  
                  "key":"isHighBandwidth",
                  "value":"IsHighBandwidth"
               }
            },
            "Operator":{  
               "name":"equals",
               "description":"=",
               "validation":"",
               "inputType":"dropdown"
            },
            "RhsValue":"true"
         },
         {  
            "LhsAttributeDefinition":{  
               "attribute":{  
                  "key":"isForMobile",
                  "value":"IsForMobile"
               }
            },
            "Operator":{  
               "name":"equals",
               "description":"=",
               "validation":"",
               "inputType":"dropdown"
            },
            "RhsValue":"true"
         }
      ],
      [  
         {  
            "LhsAttributeDefinition":{  
               "attribute":{  
                  "key":"isHighBandwidth",
                  "value":"IsHighBandwidth"
               }
            },
            "Operator":{  
               "name":"equals",
               "description":"=",
               "validation":"",
               "inputType":"dropdown"
            },
            "RhsValue":"true"
         },
         {  
            "LhsAttributeDefinition":{  
               "attribute":{  
                  "key":"isForTablet",
                  "value":"IsForTablet"
               }
            },
            "Operator":{  
               "name":"equals",
               "description":"=",
               "validation":"",
               "inputType":"dropdown"
            },
            "RhsValue":"true"
         }
      ]
   ]
}

应该会生成.Where(x => (x.isHighBandwidth == true && x.isForMobile == true) || (x.isHighBandwidth == true && x.isForTablet == true))

这是我必须使用表达式库来完成此操作的:

MethodInfo contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
Expression finalExpression = null;
List<ParameterExpression> paramsArray = new List<ParameterExpression>();
foreach (var conditionList in conditionLists)
{
    Expression andGroup = null;
    foreach (var condition in conditionList)
    {
        Expression expression = null;
        ParameterExpression param = null;
        ConstantExpression constant = null;
        switch (condition.LhsAttributeDefinition.Attribute.Key)
        {
            case "title":
                param = Expression.Parameter(typeof(string), "LearningItem.Title");
                constant = Expression.Constant(condition.RhsValue, typeof(string));
                expression = Expression.Call(param, contains, constant);
                break;
            case "isHighBandwidth":
                param = Expression.Parameter(typeof(string), "IsHighBandwidth");
                constant = Expression.Constant(condition.RhsValue, typeof(string));
                expression = Expression.Equal(param, constant);

                break;
            case "isForMobile":
                param = Expression.Parameter(typeof(string), "IsForMobile");
                constant = Expression.Constant(condition.RhsValue, typeof(string));
                expression = Expression.Equal(param, constant);

                break;
            case "isForTablet":
                param = Expression.Parameter(typeof(string), "IsForTablet");
                constant = Expression.Constant(condition.RhsValue, typeof(string));
                expression = Expression.Equal(param, constant);

                break;

        }
        paramsArray.Add(param);
        if (andGroup != null)
        {
            Expression.And(andGroup, expression);
        }
        else
        {
            andGroup = expression;
        }
    }
    //OR the expression tree created above

    if (finalExpression != null)
    {
        Expression.Or(finalExpression, andGroup);
    }
    else
    {
        finalExpression = andGroup;
    }
}
MethodCallExpression whereCallExpression = Expression.Call(
    typeof(Queryable),
    "Where",
    new Type[] { query.ElementType },
    query.Expression,
    Expression.Lambda<Func<Activity, bool>>(finalExpression, paramsArray.ToArray<ParameterExpression>()));
return query;

所以我的想法是,在嵌套的 for 循环中,我将 AND 查询和 OR 查询构建为一个大表达式,然后在最后创建 lambda 查询。我将一路上的参数收集到一个 paramsArray(列表)中。

我的问题是,在执行时,说'ParameterExpression of type 'System.String' cannot be used for delegate parameter of type 'INOLMS.Data.Activity'' 会爆炸。我假设这是因为到目前为止我收集的参数只是一个字符串(我的示例请求正文只是 IsHighBandwidth 为 true 的单个条件),而且我不喜欢使用字符串参数并尝试获取Activity 查询。

我在这里做错了什么?

【问题讨论】:

    标签: c# asp.net lambda expression


    【解决方案1】:

    您当前的代码存在很多问题。让我们逐案开始。

    假设你想改造

    {  
        "LhsAttributeDefinition":{  
           "attribute":{  
              "key":"isHighBandwidth",
              "value":"IsHighBandwidth"
           }
        },
        "Operator":{  
           "name":"equals",
           "description":"=",
           "validation":"",
           "inputType":"dropdown"
        },
        "RhsValue":"true"
    }
    

    转入.Where(x =&gt; x.IsHighBandwidth == true)

    所以首先你必须构建表达式的左侧,即x.IsHighBandwidth,并且你不能简单地定义具有常量值IsHighBandwidth 的字符串类型参数(这就是你在Expression.Parameter(typeof(string), "IsHighBandwidth") 中所做的。要做到这一点您需要Activity 类型的第一个参数,然后调用Expression.MakeMemberAccess 并使用代表所需属性的适当MemberInfo 对象。像这样:

    var p = Expression.Parameter(typeof(Activity));
    var accessorExp = Expression.MakeMemberAccess(p, typeof(Activity).GetProperty("IsHighBandwidth"));
    

    现在我们已经处理了左侧,让我们来看看右侧。如果您的属性是bool 类型并且您想要进行相等检查,那么右侧也必须匹配。您不能简单地创建字符串常量并期望某种魔法将其解析为bool 类型。在我们的例子中,我们知道我们期望 bool 值,所以我们必须首先将字符串解析为 bool,然后创建 bool 类型的常量表达式:

    bool value = Boolean.Parse(condition.RhsValue); // add error checks
    var valueExpr = Expression.Constant(value);
    

    现在我们已经处理了左侧和右侧并且类型正确,您可以像在代码中那样构造相等表达式:

    var expression = Expression.Equal(accessorExpr, valueExpr);
    

    现在我们已经构建了主体(bool 表达式类型),我们必须构造 lambda,它将作为参数传递给 lambda。正如你在 C# 代码中看到的那样,这个 lambda 只接受一个 Activity 类型的参数并返回 bool。您不能像在代码中那样发送多个参数。示例:

    // Parameter p must be the same as was defined above
    var lambda = Expression.Lambda(expression, new [] { p });
    

    现在我们有了正文,您可以像在代码中那样为 Where 构造新的方法调用表达式,但有一个重要区别:如果您希望外部参数起作用,则必须引用 lambda 表达式(这就是 LINQ @ 987654341@方法做behind the scene):

    var whereCallExpression = Expression.Call(
        typeof(Queryable),
        "Where",
        new Type[] { query.ElementType },
        query.Expression,
        Expression.Quote(lambda));
    

    这应该足够详细以帮助您入门。您必须记住,LINQ 表达式是非常低级的,您必须注意自己生成有效的表达式树。在使用 C# 编程时(例如隐式转换),没有您可能习惯的编译器魔法。

    【讨论】:

    • 哇,这非常有帮助。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-13
    • 2017-05-29
    • 2011-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多