使用LINQ ExpressionVisitor将基本参数替换为lambda表达式中的属性引用

我正在为我们系统的一部分编写数据层,该数据层记录有关每天运行的自动作业的信息 – 作业名称,运行时间,结果,等等。

我正在使用Entity Framework与数据库交谈,但我试图将这些细节隐藏在更高级别的模块之外,我不希望实体对象本身被暴露。

但是,我想使我的界面在用于查找工作信息的标准中非常灵活。 例如,用户界面应允许用户执行复杂的查询,例如“给我所有名为’hello’的作业,该作业在上午10:00到11:00之间运行失败。” 显然,这看起来像是动态构建的Expression树的工作。

所以我希望我的数据层(存储库)能够接受类型为Expression<Func> (lambda表达式)的LINQ表达式,然后在后台转换该lambda表达式,我的Entity Framework ObjectContext可以用作Where()子句中的filter。

简而言之,我正在尝试将Expression<Func>类型的lambda表达式转换为Expression<Func> ,其中svc_JobAudit是Entity Framework数据对象,对应于存储作业信息的表。 (第一个委托中的四个参数分别对应于作业名称,运行时间,结果以及分别在MS中花费的时间)

我使用ExpressionVisitor类取得了很好的进展,直到我碰到一堵砖墙并收到带有此错误消息的InvalidOperationException

从“VisitLambda”调用时,重写“System.Linq.Expressions.ParameterExpression”类型的节点必须返回相同类型的非null值。 或者,覆盖“VisitLambda”并将其更改为不访问此类型的子项。

我完全不知所措。 为什么它不会允许我将引用参数的表达式节点转换为引用属性的节点? 还有另一种方法可以解决这个问题吗?

以下是一些示例代码:

 namespace ExpressionTest { class Program { static void Main(string[] args) { Expression<Func> expression = (myString, myDateTime, myResultCode, myTimeSpan) => myResultCode == ResultCode.Failed && myString == "hello"; var result = ConvertExpression(expression); } private static Expression<Func> ConvertExpression(Expression<Func> expression) { var newExpression = Expression.Lambda<Func>(new ReplaceVisitor().Modify(expression), Expression.Parameter(typeof(svc_JobAudit))); return newExpression; } } class ReplaceVisitor : ExpressionVisitor { public Expression Modify(Expression expression) { return Visit(expression); } protected override Expression VisitParameter(ParameterExpression node) { if (node.Type == typeof(string)) { return Expression.Property(Expression.Parameter(typeof(svc_JobAudit)), "JobName"); } return node; } } } 

问题是双重的:

  • 我误解了如何访问Lambda表达式类型。 我还在返回一个与旧委托匹配的lambda,而不是返回一个新的lambda来匹配新的委托。

  • 我需要保持对新的ParameterExpression实例的引用,我没有这样做。

新代码如下所示(注意访问者现在如何接受对与Entity Framework数据对象匹配的ParameterExpression的引用):

 class Program { const string conString = @"myDB"; static void Main(string[] args) { Expression> expression = (jobName, ranAt, resultCode, elapsed) => jobName == "Email Notifications" && resultCode == (byte)ResultCode.Failed; var criteria = ConvertExpression(expression); using (MyDataContext dataContext = new MyDataContext(conString)) { List jobs = dataContext.svc_JobAudit.Where(criteria).ToList(); } } private static Expression> ConvertExpression(Expression> expression) { var jobAuditParameter = Expression.Parameter(typeof(svc_JobAudit), "jobAudit"); var newExpression = Expression.Lambda>(new ReplaceVisitor().Modify(expression.Body, jobAuditParameter), jobAuditParameter); return newExpression; } } class ReplaceVisitor : ExpressionVisitor { private ParameterExpression parameter; public Expression Modify(Expression expression, ParameterExpression parameter) { this.parameter = parameter; return Visit(expression); } protected override Expression VisitLambda(Expression node) { return Expression.Lambda>(Visit(node.Body), Expression.Parameter(typeof(svc_JobAudit))); } protected override Expression VisitParameter(ParameterExpression node) { if (node.Type == typeof(string)) { return Expression.Property(parameter, "JobName"); } else if (node.Type == typeof(DateTime)) { return Expression.Property(parameter, "RanAt"); } else if (node.Type == typeof(byte)) { return Expression.Property(parameter, "Result"); } else if (node.Type == typeof(long)) { return Expression.Property(parameter, "Elapsed"); } throw new InvalidOperationException(); } } 

接受的答案是对某些特定类型进行“硬编码”。 这是一个更通用的表达式重写器,可以替换任何其他表达式(lambda,constant,…)的参数。 在lambda表达式的情况下,表达式的签名需要更改以包含替换值所需的参数。

 public class ExpressionParameterSubstitute : System.Linq.Expressions.ExpressionVisitor { private readonly ParameterExpression from; private readonly Expression to; public ExpressionParameterSubstitute(ParameterExpression from, Expression to) { this.from = from; this.to = to; } protected override Expression VisitLambda(Expression node) { if (node.Parameters.All(p => p != this.from)) return node; // We need to replace the `from` parameter, but in its place we need the `to` parameter(s) // eg F subst F => F // eg F subst F => F var toLambda = to as LambdaExpression; var substituteParameters = toLambda?.Parameters ?? Enumerable.Empty(); ReadOnlyCollection substitutedParameters = new ReadOnlyCollection(node.Parameters .SelectMany(p => p == this.from ? substituteParameters : Enumerable.Repeat(p, 1) ) .ToList()); var updatedBody = this.Visit(node.Body); // which will convert parameters to 'to' return Expression.Lambda(updatedBody, substitutedParameters); } protected override Expression VisitParameter(ParameterExpression node) { var toLambda = to as LambdaExpression; if (node == from) return toLambda?.Body ?? to; return base.VisitParameter(node); } }