在Linq to Entities和Linq to Objects之间共享表达式

我试图在Linq to Entities调用和其他一些代码之间“共享”一组条件,以减少两个调用之间条件的可能不匹配。

我开始宣布我的条件:

private Func _submissionDateExpiredCondition = (submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK; private Func _submissionDateWithinOneWeekCondition = (submissionDate, status) => DateTime.Now < DbFunctions.AddDays(submissionDate, -7) && status == Status.Pending; private Func _bidValidityEndPeriodWithinThirtyDaysCondition = (bidValidityEndPeriod, status) => bidValidityEndPeriod.HasValue && DateTime.Now < DbFunctions.AddDays(bidValidityEndPeriod.Value, -30) && (status == Status.OK); 

然后我想在我的where子句中使用这些条件,在Linq to Entities中调用和作为if语句中的函数(或者可能是Linq to Objects查询的where调用):

 myRepository .FindAll() .Where(x => x.Property == "value" && x.Data.AnotherProperty == true && _submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status) || _submissionDateWithinOneWeekCondition(x.Data.Timestamp, x.Data.Status) || _bidValidityEndPeriodWithinThirtyDaysCondition(x.Data.ValidityStamp, x.Data.Status)) 

(请注意,MyCustomObject与myRepository.FindAll()返回的类型不同)

 private void ApplyConditions(List items) { foreach(var x in items){ if(_submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status)){ x.Property = "condition 1"; } else if(_submissionDateWithinOneWeekCondition(x.Data.Timestamp, x.Data.Status)) { x.Property = "condition 2"; } else if(_bidValidityEndPeriodWithinThirtyDaysCondition(x.Data.ValidityStamp, x.Data.Status)) { x.Property = "condition 3"; } } } 

但我一直在碰到像常规问题
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
执行存储库查询时…

我已经尝试使用谓词构建器构建谓词(根据https://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/ )但没有运气。

谁能指出我正确的方向?

晚到派对,但有人可能会发现我的攻击方式有用。 但是,如果没有一些表达式操作,就不能轻易完成。

主要问题是:在.Where的谓词表达式中,你有InvocationExpression的委托(即编译代码)。 EF无法找出该委托中的逻辑,因此无法将其转换为SQL。 这就是exception的起源。

目标是获得一个.Where谓词lambda表达式,它在逻辑上等同于你的,但是EF可以理解。 这意味着我们必须得到

 Expression> xPredicate = x => x.Property == "value" && x.Data.AnotherProperty == true && _submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status) || ...; 

 Expression> xPredicate = x => x.Property == "value" && x.Data.AnotherProperty == true && x.Data.Timestamp < DateTime.Now && x.Data.Status == Status.OK || ...; 

用于

 myRepository.FindAll().Where(xPredicate) 

,其中EntityTypeFind返回的可查询的元素类型 - 与MyCustomObject不同的元素类型。

请注意,委托的调用正在被它的定义表达式(lambda body)替换,其中(lambda)参数submissionDatestatus被调用的相应参数表达式替换。

如果将条件定义为委托,则它们的内部逻辑在编译代码中会丢失,因此我们必须从lambda表达式而不是委托开始:

 private Expression> _xSubmissionDateExpiredCondition = (submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK; // getting the delegate as before (to be used in ApplyConditions) is trivial: private Func _submissionDateExpiredCondition = _xSubmissionDateExpiredCondition.Compile(); // ... other conditions here 

使用lambda表达式而不是委托,编译器允许您重写原始谓词,如下所示:

 Expression> xPredicate = x => x.Property == "value" && x.Data.AnotherProperty == true && _xSubmissionDateExpiredCondition.Compile()(x.Data.Timestamp, x.Data.Status) || ...; 

,EF当然不会比之前更好地理解。 然而,我们实现的是条件的内部逻辑是表达式树的一部分。 所以缺少的是一些魔力:

 xPredicate = MAGIC(xPredicate); 

MAGIC作用:查找一个委托的InvocationExpression ,该委托是对lambda表达式进行Compile()方法调用的结果,并将其替换为lambda的主体,但请确保使用该变量的参数表达式替换正文中的lambda参数。调用。

在这里我的实施。 实际上, MAGIC在这里被称为Express.Prepare ,稍微不那么具体。

 ///  /// Helps in building expressions. ///  public static class Express { #region Prepare ///  /// Prepares an expression to be used in queryables. ///  /// The modified expression. ///  /// The method replaces occurrences of .Compile().Invoke(...) with the body of the lambda, with it's parameters replaced by the arguments of the invocation. /// Values are resolved by evaluating properties and fields only. ///  public static Expression Prepare(this Expression lambda) => (Expression)new PrepareVisitor().Visit(lambda); ///  /// Wrapper for . ///  public static Expression> Prepare(Expression> lambda) => lambda.Prepare(); ///  /// Wrapper for . ///  public static Expression> Prepare(Expression> lambda) => lambda.Prepare(); // NOTE: more overloads of Prepare here. #endregion ///  /// Evaluate an expression to a simple value. ///  private static object GetValue(Expression x) { switch (x.NodeType) { case ExpressionType.Constant: return ((ConstantExpression)x).Value; case ExpressionType.MemberAccess: var xMember = (MemberExpression)x; var instance = xMember.Expression == null ? null : GetValue(xMember.Expression); switch (xMember.Member.MemberType) { case MemberTypes.Field: return ((FieldInfo)xMember.Member).GetValue(instance); case MemberTypes.Property: return ((PropertyInfo)xMember.Member).GetValue(instance); default: throw new Exception(xMember.Member.MemberType + "???"); } default: // NOTE: it would be easy to compile and invoke the expression, but it's intentionally not done. Callers can always pre-evaluate and pass a captured member. throw new NotSupportedException("Only constant, field or property supported."); } } ///  ///  for . ///  private sealed class PrepareVisitor : ExpressionVisitor { ///  /// Replace lambda.Compile().Invoke(...) with lambda's body, where the parameters are replaced with the invocation's arguments. ///  protected override Expression VisitInvocation(InvocationExpression node) { // is it what we are looking for? var call = node.Expression as MethodCallExpression; if (call == null || call.Method.Name != "Compile" || call.Arguments.Count != 0 || call.Object == null || !typeof(LambdaExpression).IsAssignableFrom(call.Object.Type)) return base.VisitInvocation(node); // get the lambda var lambda = call.Object as LambdaExpression ?? (LambdaExpression)GetValue(call.Object); // get the expressions for the lambda's parameters var replacements = lambda.Parameters.Zip(node.Arguments, (p, x) => new KeyValuePair(p, x)); // return the body with the parameters replaced return Visit(new ParameterReplaceVisitor(replacements).Visit(lambda.Body)); } } ///  ///  to replace parameters with actual expressions. ///  private sealed class ParameterReplaceVisitor : ExpressionVisitor { private readonly Dictionary _replacements; ///  /// Init. ///  /// Parameters and their respective replacements. public ParameterReplaceVisitor(IEnumerable> replacements) { _replacements = replacements.ToDictionary(kv => kv.Key, kv => kv.Value); } protected override Expression VisitParameter(ParameterExpression node) { Expression replacement; return _replacements.TryGetValue(node, out replacement) ? replacement : node; } } }