我有一套像这样的lambdas
t => t.FirstName
t => t.LastName
t => t.Profession
我想找到一种方法来构建一个表达式,该表达式可以在Linq to Entities中的 Where 语句中使用,其中这些lambda与使用 string.contains的值进行比较
// a filter is definded by a lambda and the string to compare it with
var filters = new Dictionary<Expression<Func<Person, string>>, string>();
filters.Add(t => t.FirstName, "Miller");
filters.Add(t => t.Profession, "Engineer");
var filterConstraints = BuildFilterExpression(t => t, filters);
Entities.Persons.Where(filterConstraints).ToList();
public static Expression<Func<TElement, bool>> BuildFilterExpression<TElement>(Dictionary<Expression<Func<TElement, string>>, string> constraints)
{
List<Expression> expressions = new List<Expression>();
var stringType = typeof(string);
var containsMethod = stringType.GetMethod("Contains", new Type[] { stringType });
foreach (var constraint in constraints)
{
var equalsExpression = (Expression)Expression.Call(constraint.Key.Body, containsMethod, Expression.Constant(constraint.Value, stringType));
expressions.Add(equalsExpression);
}
var body = expressions.Aggregate((accumulate, equal) => Expression.And(accumulate, equal));
ParameterExpression p = constraints.First().Key.Parameters.First();
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
我想我在构建表达式树时做了一些非常错误的事情,因为我得到以下异常: 无效的操作异常 - 参数“t”未绑定在指定的LINQ to Entities查询表达式中。
有谁知道如何解决这个问题?
答案 0 :(得分:5)
你实际上真的关闭。问题是具有相同名称和类型的参数对象在技术上并不“相等”。
var b = Expression.Parameter(typeof(string), "p") ==
Expression.Parameter(typeof(string), "p");
//b is false
因此,您创建的lambda的参数是您作为输入使用的第一个表达式的参数。在所有其他表达式的主体中使用的参数是不同的参数,并且它们不作为lambda的参数给出,因此错误是因为。
解决方案实际上非常简单。您只需要将所有其他参数的所有实例替换为您要使用的实际参数。
这是一个辅助方法(使用辅助类),它在一些表达式中获取一个表达式的所有实例,并将其替换为另一个:
public class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
现在我们只需在每个主体上调用一次,替换为一个通用参数:
public static Expression<Func<TElement, bool>> BuildFilterExpression<TElement>(
Dictionary<Expression<Func<TElement, string>>, string> constraints)
{
List<Expression> expressions = new List<Expression>();
var stringType = typeof(string);
var containsMethod = stringType.GetMethod("Contains", new Type[] { stringType });
var parameter = Expression.Parameter(typeof(TElement));
foreach (var constraint in constraints)
{
var equalsExpression = (Expression)Expression.Call(
constraint.Key.Body.Replace(constraint.Key.Parameters[0], parameter),
containsMethod, Expression.Constant(constraint.Value, stringType));
expressions.Add(equalsExpression);
}
var body = expressions.Aggregate((accumulate, equal) =>
Expression.And(accumulate, equal));
return Expression.Lambda<Func<TElement, bool>>(body, parameter);
}
答案 1 :(得分:2)
关闭。不幸的是,如果您查看每个属性lambdas,例如..
t => t.FirstName
t => t.LastName
你会发现他们都是Expression.Property
。但是每个人都有不同的Expression.Parameter
。您希望使用ExpressionVisitor
将PropertyExpression.Parameter
替换为Expression.Parameter
的相同实例,并将其与Expression.Lambda
一起使用。
异常无效的操作异常 - 参数't'未绑定在指定的LINQ to Entities查询表达式中。表示你的lambda主体中有ParameterExpression
个在lambda的参数数组中。