C# 使用LINQ ExpressionVisitor用lambda表达式中的属性引用替换基元参数
我正在为我们系统的一部分编写一个数据层,记录每天运行的自动化作业的信息——作业名称、运行时间、结果等 我正在使用实体框架与数据库交谈,但我试图对更高级别的模块隐藏这些细节,我不希望实体对象本身被公开 然而,我想让我的界面在它用来查找工作信息的标准上非常灵活。例如,用户界面应该允许用户执行复杂的查询,如“给我所有在上午10:00到11:00之间运行但失败的名为‘hello’的作业”。显然,这看起来像是动态构建的C# 使用LINQ ExpressionVisitor用lambda表达式中的属性引用替换基元参数,c#,linq,lambda,expression,visitor,C#,Linq,Lambda,Expression,Visitor,我正在为我们系统的一部分编写一个数据层,记录每天运行的自动化作业的信息——作业名称、运行时间、结果等 我正在使用实体框架与数据库交谈,但我试图对更高级别的模块隐藏这些细节,我不希望实体对象本身被公开 然而,我想让我的界面在它用来查找工作信息的标准上非常灵活。例如,用户界面应该允许用户执行复杂的查询,如“给我所有在上午10:00到11:00之间运行但失败的名为‘hello’的作业”。显然,这看起来像是动态构建的表达式树的作业 因此,我希望我的数据层(存储库)能够接受Expression(lambd
表达式
树的作业
因此,我希望我的数据层(存储库)能够接受Expression
(lambda Expression)类型的LINQ表达式,然后在幕后将该lambda转换为一个表达式,我的实体框架ObjectContext
可以在Where()
子句中用作过滤器
简而言之,我试图将expression
类型的lambda表达式转换为expression
,其中svc\u JobAudit
是实体框架数据对象,它对应于存储作业信息的表。(第一个委托中的四个参数分别对应于作业的名称、运行时间、结果以及所用的时间(毫秒)
我在使用ExpressionVisitor类时取得了很好的进步,直到我撞到了砖墙,收到一个带有以下错误消息的InvalidOperationException
:
从“VisitLambda”调用时,重写类型为的节点
“System.Linq.Expressions.ParameterExpression”必须返回非空值
相同类型的值。或者,覆盖“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;
}
}
}
namespace ExpressionTest
{
班级计划
{
静态void Main(字符串[]参数)
{
表达式=(myString,myDateTime,myResultCode,myTimeSpan)=>myResultCode==ResultCode.Failed&&myString==hello;
var结果=转换表达式(表达式);
}
私有静态表达式ConvertExpression(表达式)
{
var newExpression=Expression.Lambda(new ReplaceVisitor().Modify(Expression),Expression.Parameter(typeof(svc_JobAudit));
返回新表达式;
}
}
类替换访问者:ExpressionVisitor
{
公共表达式修改(表达式)
{
回访(表达);
}
受保护的重写表达式VisitParameter(ParameterExpression节点)
{
if(node.Type==typeof(string))
{
返回Expression.Property(Expression.Parameter(typeof(svc_JobAudit)),“JobName”);
}
返回节点;
}
}
}
问题有两个方面:
- 我误解了如何访问Lambda表达式类型。我仍然返回与旧代理匹配的lambda,而不是返回与新代理匹配的新lambda
- 我需要保留对新
实例的引用,但我没有这样做参数expression
ParameterExpression
的引用):
类程序
{
const string conString=@“myDB”;
静态void Main(字符串[]参数)
{
表达式=(jobName,ranAt,resultCode,appeased)=>jobName==“电子邮件通知”&&resultCode==(字节)resultCode.Failed;
var标准=转换表达式(表达式);
使用(MyDataContext=newmydatacontext(conString))
{
List jobs=dataContext.svc_JobAudit.Where(criteria.ToList();
}
}
私有静态表达式ConvertExpression(表达式)
{
var jobAuditParameter=表达式参数(typeof(svc_JobAudit),“JobAudit”);
var newExpression=Expression.Lambda(
新访客()
.Modify(expression.Body、jobAuditParameter、jobAuditParameter);
返回新表达式;
}
}
类替换访问者:ExpressionVisitor
{
私有参数expression参数;
公共表达式修改(表达式表达式,参数Expression参数)
{
this.parameter=参数;
回访(表达);
}
受保护的重写表达式VisitLambda(表达式节点)
{
返回Expression.Lambda(Visit(node.Body)、Expression.Parameter(typeof(svc_JobAudit));
}
受保护的重写表达式VisitParameter(ParameterExpression节点)
{
if(node.Type==typeof(string))
{
返回表达式.Property(参数“JobName”);
}
else if(node.Type==typeof(DateTime))
{
返回表达式.Property(参数“RanAt”);
}
else if(node.Type==typeof(byte))
{
返回表达式.Property(参数“Result”);
}
else if(node.Type==typeof(long))
{
返回表达式.Property(参数“已用”);
}
抛出新的InvalidOperationException();
}
}
接受的答案是对某些特定类型的“硬编码”。这里有一个更通用的表达式重写器,它可以用参数替换任何其他表达式(lambda、constant等)。对于lambda表达式,exp
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();
}
}
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);
}
}