为实体框架(LINQ)构建自定义表达式

时间:2018-12-19 20:55:29

标签: c# entity-framework linq-to-entities

我有以下方法来构建一些自定义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());
}

1 个答案:

答案 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接受。