web-dev-qa-db-ja.com

LINQ where OR句と不完全な結果を返すnull値を持つラムダ式を持つ句

要するに問題

where句で使用されるラムダ式がありますが、これは「期待される」結果を返していません。

クイックサマリー

analysisObjectRepositoryオブジェクトには、Parentという名前のプロパティに親関係も含む特定のオブジェクトがあります。このanalysisObjectRepositoryをクエリして、いくつかのオブジェクトを返します。

詳細

以下のコードは、ID値を含む特定のオブジェクトのルート、最初の子(直下の子)、および孫を返すことを想定しています。

以下のコードでは、3つの個別のOR条件のいずれかを真にするすべての結果が結果のように返されるべきであると常識は言っています。

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID               == packageId ||
                    x.Parent.ID        == packageId || 
                    x.Parent.Parent.ID == packageId)
        .ToList();

ただし、上記のコードは、子と孫のみを返し、ルートオブジェクト(null Parent値)を返さず、

x.ID == packageId

条件が真。

2番目を作るオブジェクトのみ

x.Parent.ID == packageId

そして第三

x.Parent.Parent.ID == packageId

句が返されます。

以下のコードでルートオブジェクトを返すコードのみを記述した場合、それが返されるため、analysisObjectRepositoryにすべてのオブジェクトが含まれていることを完全に確認できます。

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID == packageId )
        .ToList();

ただし、デリゲートとして書き換えると、予想される結果が得られ、予想されるすべてのオブジェクトが返されます。

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(delegate(AnalysisObject x) 
        { 
            return 
              (x.ID == packageId) || 
              (x.Parent != null && x.Parent.ID == packageId) || 
                  (x.Parent != null && 
                   x.Parent.Parent != null && 
                   x.Parent.Parent.ID == packageId); })
        .ToList();

質問

ラムダ式に何か不足していますか?それは本当に単純な3つの部分OR条件であり、3つの条件のいずれかを真にするすべてのオブジェクトが返されるべきだと思います。問題が、正確にそれを理解できませんでした。

どんな助けも素晴らしいでしょう。

12
Cihan Kurt

2番目のデリゲートは、(ラムダではなく)匿名デリゲート形式の最初のデリゲートではありません。あなたの状態を見てください。

最初:

x.ID == packageId || x.Parent.ID == packageId || x.Parent.Parent.ID == packageId

第二:

(x.ID == packageId) || (x.Parent != null && x.Parent.ID == packageId) || 
(x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId)

ラムダの呼び出しは、IDが一致せず、親がnullまたは一致せず、祖父母がnullであるxに対して例外をスローします。 nullチェックをラムダにコピーすると、正しく機能するはずです。

質問へのコメント後に編集

元のオブジェクトがList<T>でない場合、FindAll()の戻り値の種類と、これがIQueryableインターフェイスを実装しているかどうかを知る方法がありません。もしそうなら、それはおそらく矛盾を説明しています。ラムダはコンパイル時にExpression<Func<T>>ただし匿名デリゲートはできないに変換できるため、ラムダバージョンを使用する場合はIQueryableの実装を使用し、使用する場合はLINQ-to-Objectsを使用する場合があります匿名委任バージョン。

これは、ラムダがNullReferenceExceptionを引き起こさない理由も説明します。そのラムダ式をIEnumerable<T>notIQueryable<T>を実装する何かに渡す場合、ラムダの実行時評価(他のメソッドと違いはありません、匿名かどうか) NullReferenceExceptionがターゲットと等しくなく、親または祖父母がnullであるオブジェクトに最初に遭遇したときに、IDをスローします。

2011年3月16日8:29 AM EDTを追加

次の簡単な例を考えてみましょう。

IQueryable<MyObject> source = ...; // some object that implements IQueryable<MyObject>

var anonymousMethod =  source.Where(delegate(MyObject o) { return o.Name == "Adam"; });    
var expressionLambda = source.Where(o => o.Name == "Adam");

これら2つの方法では、まったく異なる結果が生成されます。

最初のクエリは単純なバージョンです。匿名メソッドの結果、デリゲートがIEnumerable<MyObject>.Where拡張メソッドに渡され、sourceの内容全体がデリゲートに対して(通常のコンパイル済みコードを使用してメモリ内で)チェックされます。つまり、C#のイテレータブロックに精通している場合、次のようになります。

public IEnumerable<MyObject> MyWhere(IEnumerable<MyObject> dataSource, Func<MyObject, bool> predicate)
{
    foreach(MyObject item in dataSource)
    {
        if(predicate(item)) yield return item;
    }
}

ここでの顕著な点は、実際にフィルタリングを実行していることですメモリ内クライアント側で。たとえば、ソースがSQL ORMである場合、クエリにはWHERE句はありません。結果セット全体がクライアントに戻され、フィルタリングされますthere

ラムダ式を使用する2番目のクエリはExpression<Func<MyObject, bool>>に変換され、IQueryable<MyObject>.Where()拡張メソッドを使用します。これにより、IQueryable<MyObject>とも入力されるオブジェクトが作成されます。このすべては、expressionを基礎となるプロバイダーに渡すことで機能します。 これがNullReferenceExceptionを取得していない理由です。式(オブジェクトを使用する式のlogicの表現である、単に呼び出すことができる実際のコンパイルされた関数ではなく)をどのように変換するかは、クエリプロバイダー次第です。使える。

区別を確認する簡単な方法(または、少なくともis)を区別するには、ラムダバージョンでWhereを呼び出す前にAsEnumerable()を呼び出すことです。これにより、コードでLINQ-to-Objectsが使用されるようになり(ラムダバージョンのようにIEnumerable<T>ではなく、匿名デリゲートバージョンのようにIQueryable<T>で動作します)、次のように例外が取得されます期待される。

TL; DRバージョン

長所と短所は、ラムダ式がデータソースに対する何らかのクエリに変換されているのに対し、匿名メソッドバージョンはメモリ内のentireデータソースを評価していることです。ラムダをクエリに変換することは何でも、あなたが期待しているロジックを表していないため、期待する結果が得られません。

13
Adam Robinson

デリゲートと同じ条件でラムダを書いてみてください。このような:

  List<AnalysisObject> analysisObjects = 
    analysisObjectRepository.FindAll().Where(
    (x => 
       (x.ID == packageId)
    || (x.Parent != null && x.Parent.ID == packageId)
    || (x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId)
    ).ToList();
6

デリゲートでParentプロパティのnullをチェックしています。同じことがラムダ式でも機能するはずです。

List<AnalysisObject> analysisObjects = analysisObjectRepository
        .FindAll()
        .Where(x => 
            (x.ID == packageId) || 
            (x.Parent != null &&
                (x.Parent.ID == packageId || 
                (x.Parent.Parent != null && x.Parent.Parent.ID == packageId)))
        .ToList();
2
mgronber