使用LINQ ExpressionVisitor替换lambda表达式与属性引用语参数 [英] Using a LINQ ExpressionVisitor to replace primitive parameters with property references in a lambda expression

查看:2618
本文介绍了使用LINQ ExpressionVisitor替换lambda表达式与属性引用语参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我们的系统的一部分,它记录了约每天都运行自动化作业的信息写入数据层的过程 - 名作业,多久就跑,你结果是什么,等等

我说的是用实体框架数据库,但我想保持这些细节,从更高级别的模块隐藏起来,我不希望实体对象自己被曝光。



不过,我想使我的接口,它使用查找作业信息的标准非常灵活。例如,一个用户界面应该允许用户执行像复杂的查询给我命名为'你好'的10:00和11:00失败之间运行的所有作业。显然,这看起来像动态建表达式树木的工作。



那么,想我的数据我层(资料库)能够做的是接受型的LINQ表达式表达式来; Func键<字符串,DATETIME的ResultCode,长,BOOL>> (lambda表达式),然后幕后的拉姆达转换为我的实体框架的ObjectContext 可以作为内部过滤器的其中()使用表达式。第



在概括地说,我想类型的lambda表达式转换表达式来; Func键<字符串,DATETIME的ResultCode,长,布尔>> 表达式来; Func键< svc_JobAudit,布尔>> ,其中 svc_JobAudit 是对应于作业信息存储在表中的实体框架数据对象。 (在第一代表的四个参数对应于作业的名称,当它运行时,结果,以及多长时间分别采取了在MS,)



我使得使用 ExpressionVisitor 类,直到我撞了南墙,并获得了出现InvalidOperationException 与此错误消息了很好的进展:




在从'VisitLambda'叫,改写型
'System.Linq.Expressions.ParameterExpression'的节点必须返回一个非相同类型的-null $ b $的b值。此外,覆盖VisitLambda'和
改变它不能访问此类型的孩子。




我完全莫名其妙。见鬼,为什么不会是让我表达节点其中引用参数转换为节点所参考的属性? ?有另一种方式去了解这个



下面是一些示例代码:

 命名空间ExpressionTest 
{
类节目
{
静态无效的主要(字串[] args)
{
表达式来; Func键<字符串,日期时间,的ResultCode,长,BOOL>>表达式=(MyString的,myDateTime,myResultCode,myTimeSpan)=> myResultCode == ResultCode.Failed&放大器;&安培; MyString的==你好;
VAR的结果= ConvertExpression(表达);
}

私有静态表达式来; Func键< svc_JobAudit,布尔>> ConvertExpression(表达式来; Func键<字符串,DATETIME的ResultCode,长,BOOL>>表达式)
{
VAR newExpression = Expression.Lambda< Func键< svc_JobAudit,布尔>>(新ReplaceVisitor()。修改(表情),Expression.Parameter(typeof运算(svc_JobAudit)));
返回newExpression;
}
}

类ReplaceVisitor:ExpressionVisitor
{
公开表达修改(表达式表达式)
{
回访(表达);
}

保护覆盖表达VisitParameter(ParameterExpression节点)
{
如果(node.Type == typeof运算(字符串))
{
返回Expression.Property(Expression.Parameter(typeof运算(svc_JobAudit)),作业名);
}
返回节点;
}
}
}


解决方案

的问题是双重的:




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


  • 我需要举行新的参考 ParameterExpression 实例,这是我没有做的事情。




新的代码如下所示(请注意访问者现在怎么接受一个 ParameterExpression 匹配实体框架的数据对象的引用):

 类节目
{
常量字符串conString = @MYDB

静态无效的主要(字串[] args)
{
表达式来; Func键<字符串,日期时间,字节,长的,布尔>>表达式=(工作名,ranAt,resultCode为,经过)=>工作名==电子邮件通知&放大器;&安培; resultCode为==(字节)ResultCode.Failed;
VAR标准= ConvertExpression(表达); svc_JobAudit>使用(MyDataContext的DataContext =新MyDataContext(conString))
{
名单,LT

;工作= dataContext.svc_JobAudit.Where(标准).ToList();
}
}

私有静态表达式来; Func键< svc_JobAudit,布尔>> ConvertExpression(表达式来; Func键<字符串,日期时间,字节,长的,布尔>>表达式)
{
VAR jobAuditParameter = Expression.Parameter(typeof运算(svc_JobAudit),jobAudit);
VAR newExpression = Expression.Lambda<&Func键LT; svc_JobAudit,布尔>>(新ReplaceVisitor()修改(expression.Body,jobAuditParameter),jobAuditParameter。);
返回newExpression;
}
}

类ReplaceVisitor:ExpressionVisitor
{
私人ParameterExpression参数;

公开表达修改(表达表达,ParameterExpression参数)
{
this.parameter =参数;
回访(表达);
}

保护覆盖表达VisitLambda< T>(表达式来; T>节点)
{
返回Expression.Lambda< Func键< svc_JobAudit,布尔>>(访问(node.Body),Expression.Parameter(typeof运算(svc_JobAudit)));
}

保护覆盖表达VisitParameter(ParameterExpression节点)
{
如果(node.Type == typeof运算(字符串))
{
返回Expression.Property(参数,作业名);
}
,否则如果(node.Type == typeof运算(DATETIME))
{
返回Expression.Property(参数RanAt);
}
,否则如果(node.Type == typeof运算(字节))
{
返回Expression.Property(参数,结果);
}
,否则如果(node.Type == typeof运算(长))
{
返回Expression.Property(参数,经历);
}
抛出新的InvalidOperationException异常();
}
}


I'm in the process of writing a data layer for a part of our system which logs information about automated jobs that run every day - name of the job, how long it ran, what the result was, etc.

I'm talking to the database using Entity Framework, but I'm trying to keep those details hidden from higher-level modules and I don't want the entity objects themselves to be exposed.

However, I would like to make my interface very flexible in the criteria it uses to look up job information. For example, a user interface should allow the user to execute complex queries like "give me all jobs named 'hello' which ran between 10:00am and 11:00am that failed." Obviously, this looks like a job for dynamically-built Expression trees.

So what I'd like my data layer (repository) to be able to do is accept LINQ expressions of type Expression<Func<string, DateTime, ResultCode, long, bool>> (lambda expression) and then behind the scenes convert that lambda to an expression that my Entity Framework ObjectContext can use as a filter inside a Where() clause.

In a nutshell, I'm trying to convert a lambda expression of type Expression<Func<string, DateTime, ResultCode, long, bool>> to Expression<Func<svc_JobAudit, bool>>, where svc_JobAudit is the Entity Framework data object which corresponds to the table where job information is stored. (The four parameters in the first delegate correspond to the name of the job, when it ran, the result, and how long it took in MS, respectively)

I was making very good progress using the ExpressionVisitor class until I hit a brick wall and received an InvalidOperationException with this error message:

When called from 'VisitLambda', rewriting a node of type 'System.Linq.Expressions.ParameterExpression' must return a non-null value of the same type. Alternatively, override 'VisitLambda' and change it to not visit children of this type.

I'm completely baffled. Why the heck won't it allow me to convert expression nodes which reference parameters to nodes which reference properties? Is there another way to go about this?

Here is some sample code:

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;
        }
    }
}

解决方案

The problem was two-fold:

  • I was misunderstanding how to visit the Lambda expression type. I was still returning a lambda which matched the old delegate instead of returning a new lambda to match the new delegate.

  • I needed to hold a reference to the new ParameterExpression instance, which I wasn't doing.

The new code looks like this (notice how the visitor now accepts a reference to a ParameterExpression matching the Entity Framework data object):

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();
    }
}

这篇关于使用LINQ ExpressionVisitor替换lambda表达式与属性引用语参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆