1

オブジェクト グラフ内のアイテムを更新しようとしていますが、これは深さが 'n' レベルに及ぶ可能性があります。

以下は私のオブジェクトモデルです:

   public class Entity
    {
        public string Name { get; set; }
    }

    public class Category : Entity
    {        
        public List<Category> Categories { get; set; }
        public List<Product> Products { get; set; }      
    }

    public class Product : Entity
    {        
    }

私のビューは にバインドされていObservableCollection<Category> Categoriesます。私がやりたいのは、カテゴリ名を指定することです。コレクションからそれに一致する最初のオブジェクトを取得する必要があります。

たとえば、このようなリストとフェイシャル ティッシュ カテゴリがある場合、コレクションからフェイシャル ティッシュ カテゴリ オブジェクトを取得する必要があります。

Category - Pharmacy
  |-Product - Aspirin
  |-Product - Tylenol
  |-Category - Tooth Paste
  |  |-Product - Crest
  |  |-Product - Colgate
  |-Category - Paper Products
   |-Category - Toilet Paper
   |  |-Product - NoName
   |  |-Product - Charmin
   |-Category - Facial Tissue
      |-Product - Kleenex
Category - Household
  |-Product - Pinesol Cleaner
  |-Product - Garbage Bags

私はこれを試しましたが、階層でレベル > 2 を検索すると、オブジェクト例外のインスタンスに設定されていないオブジェクト参照がスローされます。

 return Categories.FirstOrDefault(n => n.Name == name) ??
                   Categories.SelectMany(node => node.Categories).Where(lx => lx.Name == name).FirstOrDefault();

注:場合によっては、カテゴリが階層の奥深くで null になることがあります。つまり、カテゴリがない場合、コレクションは null に設定されます。また、ソリューションで必ずしも LINQ を使用する必要はありません。

4

3 に答える 3

4

次のいずれかの方法を使用して、ツリー構造を再帰的にトラバースできます。

public static IEnumerable<T> Traverse<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> childSelector)
{
    var queue = new Queue<T>(source);
    while (queue.Any())
    {
        var item = queue.Dequeue();
        yield return item;
        foreach (var child in childSelector(item))
        {
            queue.Enqueue(child);
        }
    }
}

public static IEnumerable<T> Traverse<T>(T root, Func<T, IEnumerable<T>> childSelector)
{
    return Traverse(new[] { root }, childSelector);
}

単一のルートアイテムにはオーバーロードがあり、別のルートアイテムには一連のアイテムが必要です。

必要に応じて、実際の再帰を使用してそれらを実装することもできますが、私は明示的なデータ構造を好みます。幅優先探索ではなく深さ優先探索が必要な場合は、をに変更し、QueueそれStackに応じてメソッドを更新してください。

それを使用するには、次のようなことを行うことができます。

Category root = new Category();
var searchResult = Traverse(root, item => item.Categories)
            .Where(category => category.Name == "testValue")
            .FirstOrDefault();

また、nullがあるため、nullエラーが発生しているようですCategories。可能であれば、問題に対処するのではなく、その問題を修正することを強くお勧めします。エンティティにカテゴリがない場合は、nullリストではなく、空のリストが必要です。Traverseそうは言っても、nullアイテムがある場合は、次のように呼び出しを調整できます。

Traverse(root, item => item.Categories ?? Enumerable.Empty<Category>())
于 2012-12-10T17:16:07.643 に答える
1

LINQ自体には、深さ優先探索専用の演算子はありません(この場合はこれが必要です)。ただし、要件を考えると、単純な再帰関数を使用したかなり簡単な解決策があります。

// Returns the first category with the given name or null, if none is found
Category findCategory(Category start, String name) {
    if (start.name == name) {
        return start;
    }
    if (start.Categories == null) {
        return null;
    }
    return (from c in start.Categories
            let found = findCategory(c, name)
            where found != null
            select found).FirstOrDefault()
}

Categoriesサブカテゴリのないカテゴリのプロパティを、の代わりに空のリストに設定することを検討してくださいnull。これにより、ここでnullチェックをスキップできます(おそらく他の多くの場所でも)。

于 2012-12-10T17:15:38.300 に答える
0

Linq だけを使用するわけではありませんが、これは簡単な解決策です。

public Category GetCategory(string name, List<Category> Categories) 
{
    Category found = Categories.FirstOrDefault(cat => cat.Name == name);
    return found ?? Categories.Select(cat => GetCategory(name,cat.Categories))
                              .FirstOrDefault(cat => cat != null);
}
于 2012-12-10T17:33:32.623 に答える