6

我有一些代码生成表达式作为数据库读取中的“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 值?

4

2 回答 2

7

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

就像是:

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 倍:使用丹尼斯的测量代码,我得到以下结果:

Make expression: 00:00:02.9869921
Replace constant expression: 00:00:02.3332857
Set property: 00:00:00.0056485
于 2013-02-28T21:53:07.543 回答
5

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

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
完成。

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

于 2013-02-28T12:19:29.853 回答