代码之家  ›  专栏  ›  技术社区  ›  Jon Egerton Aditya kumar sahoo

更改预先存在的BinaryExpression中的ConstantExpression的值

  •  6
  • Jon Egerton Aditya kumar sahoo  · 技术社区  · 11 年前

    我有一些代码生成表达式要作为数据库中的“where”语句读取来传递,我正在努力加快速度。

    下面的示例使用where语句将表的PK与传入的值相匹配:

    private Expression MakeWhereForPK(int id)
    {
        var paramExp = Expression.Parameter(typeof(Brand),"b");
    
        //Expression to get value from the entity
        var leftExp = Expression.Property(paramExp,"ID");
    
        //Expression to state the value to match (from the passed in variable)
        var rightExp = Expression.Constant(id,typeof(int));
    
        //Expression to compare the two
        var whereExp = Expression.Equal(leftExp,rightExp);
    
        return Expression.Lambda<Func<Brand,bool>>(whereExp,paramExp);
    }
    

    上面是对这个问题的简化-真实的东西包括用来查询表并找到其PK等的代码。它有效地做了与你通常在代码中做的相同的事情:

    ctx.Brands.Where(b => b.ID = id);
    

    这还可以,但是,在进行测试以优化时,我发现它相当慢——完成上述1000000次大约需要25秒。如果我省略上面的最后一行会更好(但显然这是无用的!),所以它看起来是Expression.Lamba,大约需要2/3的时间,但其余的也不太好。

    如果所有的查询都将同时发生,我可以将其转换为 IN 样式表达式并生成一次,但不幸的是,这是不可能的,所以我希望节省上面的大部分生成,只重用生成的表达式,但传递不同的值 id .

    请注意,由于这将被传递给Linq,我无法编译表达式以获得一个整数参数,我可以在调用时传递该参数——它必须保留为表达式树。

    因此,为了进行计时练习,以下可能是一个简单的版本:

    Expression<Func<Brand,bool>> savedExp;
    
    private Expression MakeWhereForPKWithCache(int id)
    {
        if (savedExp == null)
        {
            savedExp = MakeWhereForPK(id);
        }
        else
        {
            var body = (BinaryExpression)savedExp.Body;
            var rightExp = (ConstantExpression)body.Right;
    
            //At this point, value is readonly, so is there some otherway to "inject" id, 
            //and save on compilation?
            rightExp.Value = id;
        }
    
        return savedExp;
    }
    

    我如何重新使用这个表达式,只是使用不同的id值?

    2 回复  |  直到 11 年前
        1
  •  7
  •   svick bala    11 年前

    您可以使用这样一个事实,即表达式树不必只包含简单的常量,它还可以包含访问的属性。因此,您要做的是创建一个访问某个属性值的表达式,每次只更改该属性,而不更改表达式树。

    类似于:

    class ExpressionHolder
    {
        public int Value { get; set; }
    
        public Expression<Func<Brand, bool>> Expr { get; private set; }
    
        public ExpressionHolder()
        {
            Expr = MakeWhereForPK();
        }
    
        private Expression<Func<Brand, bool>> MakeWhereForPK()
        {
            var paramExp = Expression.Parameter(typeof(Brand), "b");
    
            var leftExp = Expression.Property(paramExp, "ID");
    
            var rightExp = Expression.Property(Expression.Constant(this), "Value");
    
            var whereExp = Expression.Equal(leftExp, rightExp);
    
            return Expression.Lambda<Func<Brand, bool>>(whereExp, paramExp);
        }
    }
    

    这比你的代码快大约500:使用Dennis的测量代码,我得到了以下结果:

    Make expression: 00:00:02.9869921
    Replace constant expression: 00:00:02.3332857
    Set property: 00:00:00.0056485
    
        2
  •  5
  •   Alexander Derck    8 年前

    你不能改变表达式树——它们是不可变的。但是,您可以通过创建自己的访问者来替换常量表达式:

    class MyVisitor : ExpressionVisitor
    {
        private readonly ConstantExpression newIdExpression;
    
        public MyVisitor(int newId)
        {
            this.newIdExpression = Expression.Constant(newId);
        }
    
        public Expression ReplaceId(Expression sourceExpression)
        {
            return Visit(sourceExpression);
        }
    
        protected override Expression VisitConstant(ConstantExpression node)
        {
            return newIdExpression;
        }
    }
    

    用法:

                var expr = MakeWhereForPK(0); // p => p.ID == 0
                var visitor = new MyVisitor(1);
                var newExpr = visitor.ReplaceId(expr); p => p.ID == 1
    

    请注意,这会复制现有的树。 ,而我还没有对此进行性能测试。我不确定,这会不会更快。

    此代码:

                // warming up
                var visitor = new MyVisitor(1);
                var expr = MakeWhereForPK(0);
                visitor.ReplaceId(MakeWhereForPK(0));
    
                var sw = new System.Diagnostics.Stopwatch();
                sw.Start();
    
                for (var i = 0; i < 1000000; i++)
                {
                    MakeWhereForPK(i);
                }
    
                sw.Stop();
                Console.WriteLine("Make expression: {0}", sw.Elapsed);
    
                sw.Restart();
    
                for (var i = 0; i < 1000000; i++)
                {
                    visitor.Visit(expr);
                }
    
                sw.Stop();
                Console.WriteLine("Replace constant expression: {0}", sw.Elapsed);
    
                Console.WriteLine("Done.");    
    

    在我的机器上生成以下结果:

    生成表达式:00:00:04.1714254
    替换常量表达式:00:00:02.3644953
    完成。

    看起来访问者比创建新表达式更快。