用于创建动态Where子句的表达式树抛出与参数相关的错误

Ale*_*ler 2 c# asp.net lambda expression

我收到了客户发送来的条件清单。我需要获取此列表并创建一个由 EntityFramework 执行的动态 where 子句。

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

每个条件列表都需要进行“与”运算。

每个条件列表列表都需要进行“或”运算。

所以如果我们有

{  
   "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"
         }
      ]
   ]
}
Run Code Online (Sandbox Code Playgroud)

那应该生成.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;
Run Code Online (Sandbox Code Playgroud)

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

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

我在这里做错了什么?

nej*_*jcs 5

您当前的代码存在很多问题。让我们从具体情况开始。

假设你想转型

{  
    "LhsAttributeDefinition":{  
       "attribute":{  
          "key":"isHighBandwidth",
          "value":"IsHighBandwidth"
       }
    },
    "Operator":{  
       "name":"equals",
       "description":"=",
       "validation":"",
       "inputType":"dropdown"
    },
    "RhsValue":"true"
}
Run Code Online (Sandbox Code Playgroud)

进入.Where(x => 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"));
Run Code Online (Sandbox Code Playgroud)

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

bool value = Boolean.Parse(condition.RhsValue); // add error checks
var valueExpr = Expression.Constant(value);
Run Code Online (Sandbox Code Playgroud)

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

var expression = Expression.Equal(accessorExpr, valueExpr);
Run Code Online (Sandbox Code Playgroud)

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

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

现在我们有了主体,您可以Where像在代码中那样构造新的方法调用表达式,但有一个重要的区别:如果您希望外部参数起作用,则必须引用 lambda 表达式(这就是 LINQWhere方法在幕后所做的事情):

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

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