表达式树创建动态Where子句抛出与params相关的错误

时间:2017-08-23 17:11:07

标签: c# asp.net lambda expression

我已经获得了从客户端发送的条件列表。我需要获取此列表并创建一个由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))

以下是我使用Expression库完成此任务的目的:

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的单一条件),并且它不像我那样#39 ; m采用字符串参数并尝试获取Activity查询。

我在这里做错了什么?

1 个答案:

答案 0 :(得分:2)

您目前拥有的代码存在很多问题。让我们从个案开始。

假设您想要转换

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

进入.Where(x => x.IsHighBandwidth == true)

首先,您必须构建表达式的左侧x.IsHighBandwidth,并且您不能简单地使用常量值IsHighBandwidth定义类型字符串的参数(这是您在{{1}中所做的要执行此操作,您需要第一个类型为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);

现在我们已经构建了body(带有var expression = Expression.Equal(accessorExpr, valueExpr); 表达式类型),我们必须构造lambda,它将作为参数传递给lambda。正如您在C#代码中看到的那样,这个lambda只接受一个bool类型的参数并返回Activity。您不能像在代码中那样发送多个参数。例如:

bool

现在我们有了body,你可以像在代码中那样构造// Parameter p must be the same as was defined above var lambda = Expression.Lambda(expression, new [] { p }); 的新方法调用表达式,但有一个重要区别:如果你想让外部参数工作,你必须引用lambda表达式(这就是LINQ Where方法执行behind the scene):

Where

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