我有一套像這樣的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查詢表達式中。
有誰知道如何解決這個問題?
你真的很親密。問題是具有相同名稱和類型的參數對像在技術上並不“相等”。
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);
}
關。不幸的是,如果你看看你的每個屬性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的參數數組中。