我有以下方法来构建一些自定义EF查询,以支持非常接近工作的文本过滤器,但是组合表达式的LEFT端存在问题。当我使用“ Expression.Invoke”(方法主体的第一行)时,出现一个The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
异常对我有意义(我从概念上理解LINQ => SQL转换中发生的事情)。因此,我认为表达式的左侧必须更像右侧(即使用Expression.Constant
),在此所有的“预处理”都已完成,因此LINQ to Entities知道如何构造表达式的左侧。 / p>
但是当我使用第二行(Expression.Property
)时,出现异常:
Instance property 'PropertyName' is not defined for type System.Func2[Proj.EntityFramework.DomainObject,System.Decimal]'
我了解...。少得多。
对相关方法的示例调用:
return context.DomainObjects.Where(BuildExpression(l => l.PropertyName, "<200"));
因此,我大致了解到我正在构建错误的表达式,并且它试图从提供的表达式中提取属性名称,而不是从EF编译SQL语句所需的任何东西开始,但是此时我有点迷失了。
private static Expression<Func<DomainObject, bool>> BuildExpression<TDest>(
Expression<Func<DomainObject, TDest>> propertyexpression,
string term
) where TDest : struct {
//var property = Expression.Invoke(propertyexpression, propertyexpression.Parameters.ToArray());
var property = Expression.Property(propertyexpression, ((MemberExpression)propertyexpression.Body).Member.Name);
var parser = new ParsedSearchTerm<TDest>(term); // e.g. "<200" => { LowerBound = null, Operator = "<", UpperBound = 200 }
Expression final = null;
if (parser.HasLowerBound) {
final = Expression.AndAlso(
Expression.GreaterThanOrEqual(property, Expression.Constant(parser.LowerBound)),
Expression.LessThanOrEqual(property, Expression.Constant(parser.UpperBound)));
}
else {
switch (parser.Operator) {
case "<":
final = Expression.LessThanOrEqual(property, Expression.Constant(parser.UpperBound));
break;
case ">":
final = Expression.GreaterThanOrEqual(property, Expression.Constant(parser.UpperBound));
break;
case "=":
final = Expression.Equal(property, Expression.Constant(parser.UpperBound));
break;
case "!":
final = Expression.Negate(Expression.Equal(property, Expression.Constant(parser.UpperBound)));
break;
}
}
return Expression.Lambda<Func<DomainObject, bool>>(final, propertyexpression.Parameters.ToArray());
}
答案 0 :(得分:2)
要使您的代码将Invoke
手动扩展为lambda主体,您需要将lambda参数(propertyexpression
)的主体用作要测试的property
值:
var property = propertyexpression.Body;
(我将propertyexpression
重命名为propertylambda
-实际上,propertyexpression.Body
是属性表达式)。
如果用扩展名替换Invoke
的扩展名可以对propertylambda
的lambda主体进行原位扩展,并用替换为lambda参数的参数进行扩展,则可以将原始lambda与EF一起使用。我称之为Apply
。
给出一些Expression
扩展方法:
public static class ExpressionExt {
/// <summary>
/// Replaces a sub-Expression with another Expression inside an Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
public static Expression PropagateNull(this Expression orig) => new NullVisitor().Visit(orig);
public static Expression Apply(this LambdaExpression e, params Expression[] args) {
var b = e.Body;
foreach (var pa in e.Parameters.Zip(args, (p, a) => (p, a)))
b = b.Replace(pa.p, pa.a);
return b.PropagateNull();
}
}
和一些ExpressionVisitor
类来进行更改:
/// <summary>
/// Standard ExpressionVisitor to replace an Expression with another in an Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
/// <summary>
/// ExpressionVisitor to replace a null.member Expression with a null
/// </summary>
public class NullVisitor : System.Linq.Expressions.ExpressionVisitor {
public override Expression Visit(Expression node) {
if (node is MemberExpression nme && nme.Expression is ConstantExpression nce && nce.Value == null)
return Expression.Constant(null, nce.Type.GetMember(nme.Member.Name).Single().GetMemberType());
else
return base.Visit(node);
}
}
您可以采用Expression.Invoke(lambda,args)
的任何实例并将其替换为Apply(lambda, args)
,它将内联扩展lambda主体,以便EF接受。