6

データベース読み取りの「where」ステートメントとして渡す式を生成するコードがいくつかあり、少しスピードアップしようとしています。

次の例では、テーブルの PK と渡された値を照合する where ステートメントを作成します。

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 秒かかります。上記の最後の行を省略した方が良いでしょう (しかし、明らかにそれは役に立たない!)。そのため、約 2/3 の時間を取っているのは Expression.Lamba のように見えますが、残りもあまり良くありません。

すべてのクエリが一度に発生する場合は、それを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倍高速です。Dennisの測定コードを使用すると、次の結果が得られます。

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 に答える