![](/img/trans.png)
[英]Identify parentheses in lambda expression with ExpressionVisitor in C#
[英]Using a LINQ ExpressionVisitor to replace primitive parameters with property references in a lambda expression
我正在为我们系统的一部分编写数据层,该数据层记录有关每天运行的自动作业的信息 - 作业名称,运行时间,结果,等等。
我正在使用Entity Framework与数据库交谈,但我试图将这些细节隐藏在更高级别的模块之外,我不希望实体对象本身被暴露。
但是,我想使我的界面在用于查找作业信息的标准中非常灵活。 例如,用户界面应允许用户执行复杂的查询,例如“给我所有名为'hello'的作业,该作业在上午10:00到11:00之间运行失败。” 显然,这看起来像是动态构建的Expression
树的工作。
所以我希望我的数据层(存储库)能够做的是接受Expression<Func<string, DateTime, ResultCode, long, bool>>
(lambda expression)类型的LINQ表达式,然后在后台转换lambda表达式,我的Entity Framework ObjectContext
可以用作Where()
子句中的过滤器。
简而言之,我正在尝试将Expression<Func<string, DateTime, ResultCode, long, bool>>
类型的lambda表达式转换为Expression<Func<svc_JobAudit, bool>>
,其中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<string, DateTime, ResultCode, long, bool>> expression = (myString, myDateTime, myResultCode, myTimeSpan) => myResultCode == ResultCode.Failed && myString == "hello";
var result = ConvertExpression(expression);
}
private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, ResultCode, long, bool>> expression)
{
var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(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<Func<string, DateTime, byte, long, bool>> expression = (jobName, ranAt, resultCode, elapsed) => jobName == "Email Notifications" && resultCode == (byte)ResultCode.Failed;
var criteria = ConvertExpression(expression);
using (MyDataContext dataContext = new MyDataContext(conString))
{
List<svc_JobAudit> jobs = dataContext.svc_JobAudit.Where(criteria).ToList();
}
}
private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, byte, long, bool>> expression)
{
var jobAuditParameter = Expression.Parameter(typeof(svc_JobAudit), "jobAudit");
var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(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<T>(Expression<T> node)
{
return Expression.Lambda<Func<svc_JobAudit, bool>>(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<T>(Expression<T> 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)
// e.g. F<DateTime,Bool> subst F<Source,DateTime> => F<Source,bool>
// e.g. F<DateTime,Bool> subst F<Source1,Source2,DateTime> => F<Source1,Source2,bool>
var toLambda = to as LambdaExpression;
var substituteParameters = toLambda?.Parameters ?? Enumerable.Empty<ParameterExpression>();
ReadOnlyCollection<ParameterExpression> substitutedParameters
= new ReadOnlyCollection<ParameterExpression>(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);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.