我已经获得了从客户端发送的条件列表。我需要获取此列表并创建一个由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
查询。
我在这里做错了什么?
答案 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#中编程时,您可能习惯使用编译器魔法(例如隐式转换)。