1

問題の例を次に示します。

   var source = new LambdasTestEntity[] { 
        new LambdasTestEntity {Id = 1},
        new LambdasTestEntity {Id = 2},          
        new LambdasTestEntity {Id = 3},
        new LambdasTestEntity {Id = 4},          
    };

    Expression<Func<LambdasTestEntity, bool>> expression1 = x => x.Id == 1;
    Expression<Func<LambdasTestEntity, bool>> expression2 = x => x.Id == 3;
    Expression<Func<LambdasTestEntity, bool>> expression3 = x => x.Id > 2;

    // try to chain them together in a following rule
    // Id == 1 || Id == 3 && Id > 2
    // as && has higher precedence, we expect getting two entities
    // with Id=1 and Id=3 

    // see how default LINQ works first
    Expression<Func<LambdasTestEntity, bool>> expressionFull = x => x.Id == 1 || x.Id == 3 && x.Id > 2;

    var filteredDefault = source.AsQueryable<LambdasTestEntity>()
              .Where(expressionFull).ToList();

    Assert.AreEqual(2, filteredDefault.Count); // <-this passes

    // now create a chain with predicate builder
    var totalLambda = expression1.Or(expression2).And(expression3);

    var filteredChained = source.AsQueryable<LambdasTestEntity>()
              .Where(totalLambda).ToList();


    Assert.AreEqual(2, filteredChained.Count);
    // <- this fails, because PredicateBuilder has regrouped the first expression,
    // so it now looks like this: (Id == 1 || Id == 3) && Id > 2

両方の式の Watches を調べると、次のように表示されます。

expressionFull as it is coming from Linq:
(x.Id == 1) OrElse ((x.Id == 3) AndAlso (x.Id > 2))

totalLambda for PredicateBuilder:
((x.Id == 1) OrElse Invoke(x => (x.Id == 3), x)) AndAlso Invoke(x => (x.Id > 2), x)

PredicateBuilder の動作がデフォルトの Linq Expression Builder と異なる場合、PredicateBuilder を使用するのは少し安全ではないと思います。

ここでいくつかの質問:

1) Linq がこれらのグループを作成するのはなぜですか? Or式を作っても

x => x.Id == 1 || x.Id == 3 || x.Id > 2

最初の 2 つの基準は、次のようにグループ化されています。

((x.Id == 1) OrElse (x.Id == 3)) OrElse (x.Id > 2)

なぜそれだけではないのか

(x.Id == 1) OrElse (x.Id == 3) OrElse (x.Id > 2)

?

2) PredicateBuilder がこれらの呼び出しを追加するのはなぜですか? デフォルトのLinq式の結果にInvokesが表示されないため、役に立たないようです...

3) 式を「オフライン」で構築し、デフォルトの Linq 式ビルダーに渡す他の方法はありますか? このようなもの:

ex = x => x.Id == 1;
ex = ex || x.Id == 3;
ex = ex && x.Id > 2;

次に、Linq 式ビルダーがそれを解析し、x => x.Id == 1 || の場合と同じ式を作成します。x.Id == 3 && x.Id > 2 (&& を優先)? または、PredicateBuilder を微調整して同じことを行うことはできますか?

4

1 に答える 1

3

上記の私のコメントを拡張します。

ここには演算子の優先順位の概念がないためです。文字通り自分で式ツリーを構築していて、あるメソッドの結果を次のメソッドに「パイプ」することで順序が決まります。したがって、結果の式の順序は、指定したとおりになります。

の完全なソースはここPredicateBuilderに投稿されており、それがいかに簡単かを示しています。ただし、上記の問題の原因も示しています。Albahari のサイトにアクセスしたくない場合は、完全なソースを以下に示します。

public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
                                                 Expression<Func<T, bool>> expr2)
{
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
         (Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
}

public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
                                                  Expression<Func<T, bool>> expr2)
{
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
         (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
}

ここで注目すべき主な点は、一度に 1 つのノードで式を作成し、このノードを後続のノードの左側の式 (リーフ) としてパイプ処理することです。このExpression.Invoke呼び出しは、既存のノードから右側の葉 (次の式) にパラメーターをパイプするだけであり、残りは自明です。

編集:これと同様のことをしなければなりませんでした(ただし、PredicateBuilder を使用せず、Expression呼び出しを使用して自分でツリーを構築しました)。心に留めておくべき主なことは、And/AndAlso最初にノードを処理してからノードを処理するだけでよいということOr/OrElseです。これにより、適切な優先順位でツリーを構築できます。残念ながら、ExpressionTrees手動での構築は段階的なプロセスであるため、必要な結果を得るには、各手順を正しい順序に分解する必要があります。

于 2013-01-23T16:54:26.883 に答える