web-dev-qa-db-ja.com

すべての子を1つのリストに入れる-再帰C#

C#| .NET 4.5 |エンティティフレームワーク5

Entity Frameworkに次のようなクラスがあります。

public class Location
{
   public long ID {get;set;}
   public long ParentID {get;set;}
   public List<Location> Children {get;set;}
}

IDはロケーションの識別子、ParentIDはロケーションを親にリンクし、Childrenは親ロケーションのすべての子ロケーションを含みます。すべての "Location"とその子をLocation.IDを含む単一のリストに取得するための、おそらく再帰的な簡単な方法を探しています。これを再帰的に概念化することに問題があります。どんな助けでもありがたいです。

これは私がこれまで持ってきたもので、エンティティークラスへの拡張ですが、私はそれがより良く/より簡単にできると信じています:

public List<Location> GetAllDescendants()
{
    List<Location> returnList = new List<Location>();
    List<Location> result = new List<Location>();
    result.AddRange(GetAllDescendants(this, returnList));
    return result;
}

public List<Location> GetAllDescendants(Location oID, ICollection<Location> list)
{
    list.Add(oID);
    foreach (Location o in oID.Children)
    {
            if (o.ID != oID.ID)
                    GetAllDescendants(o, list);
    }
    return list.ToList();
}

[〜#〜]更新[〜#〜]

結局、SQLで再帰を記述し、それをSPでスローし、それをEntityに取り込みました。 Linqを使用するよりもクリーンで簡単に思え、LinqとEntityのコメントから判断すると、最適なルートとは思えません。助けてくれてありがとう!

16
Will

あなたがすることができます SelectMany

List<Location> result = myLocationList.SelectMany(x => x.Children).ToList();

あなたはいくつかの選択的な結果のために条件を使用することができます

List<Location> result = myLocationList.Where(y => y.ParentID == someValue)
                                      .SelectMany(x => x.Children).ToList();

Id's of Childrenのみが必要な場合は、

List<long> idResult = myLocationList.SelectMany(x => x.Children)
                                    .SelectMany(x => x.ID).ToList();
19
Nikhil Agrawal

この拡張メソッドを試してください:

public static IEnumerable<T> Flatten<T, R>(this IEnumerable<T> source, Func<T, R> recursion) where R : IEnumerable<T>
{
    return source.SelectMany(x => (recursion(x) != null && recursion(x).Any()) ? recursion(x).Flatten(recursion) : null)
                 .Where(x => x != null);
}

そして、あなたはこのようにそれを使うことができます:

locationList.Flatten(x => x.Children).Select(x => x.ID);
11
Jonathas Costa

これはトリックを行います:

class Extensions
{
    public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
    {
        var result = source.SelectMany(selector);
        if (!result.Any())
        {
            return result;
        }
        return result.Concat(result.SelectManyRecursive(selector));
    }
}

次のように使用します。

List<Location> locations = new List<Location>();
//
// your code here to get locations
//
List<string> IDs = locations.SelectManyRecursive(l => l.Children).Select(l => l.ID).ToList();
8

以下の参考文献から変更された独自のソリューションを提供したいと思います。

public static IEnumerable<T> Flatten<T, R>(this IEnumerable<T> source, Func<T, R> recursion) where R : IEnumerable<T>
{
    var flattened = source.ToList();

    var children = source.Select(recursion);

    if (children != null)
    {
        foreach (var child in children)
        {
            flattened.AddRange(child.Flatten(recursion));
        }
    }

    return flattened;
}

例:

var n = new List<FamilyMember>()
{
    new FamilyMember { Name = "Dominic", Children = new List<FamilyMember>() 
        {
            new FamilyMember { Name = "Brittany", Children = new List<FamilyMember>() }
        }
    }
}.Flatten(x => x.Children).Select(x => x.Name);

出力:

  • ドミニク
  • ブルターニュ

クラス:

public class FamilyMember {
    public string Name {get; set;}
    public List<FamilyMember> Children { get; set;}
}

参照 https://stackoverflow.com/a/21054096/1477388

注:他のリファレンスは見つかりませんが、SO=の別の誰かが、コードをコピーした回答を公開しました。

5
user1477388

エンティティフレームワークは現在再帰をサポートしていません。そのため、次のいずれかを行うことができます。

  • 行ったように子コレクションの遅延読み込みに依存する(N + 1問題に注意)
  • オブジェクトの任意の深さをクエリします(これは醜いクエリですが、System.Linq.Expressionsを使用して生成することもできます)。

唯一の実際のオプションは、LINQを使用してクエリを表現することを避け、代わりに標準SQLに頼ることです。

エンティティフレームワークは、最初にコードを使用しているかどうかにかかわらず、このシナリオをかなりうまくサポートします。

コードファーストの場合、次のように考えてください。

var results = this.db.Database.SqlQuery<ResultType>(rawSqlQuery)

モデルファーストの場合は、 defining query を使用することを検討してください。これにより、追加の構成またはストアドプロシージャが可能になるため、これは良いオプションだと思います。

データを再帰的に取得するには、SQL Serverを使用していて、それがバージョン2005以降であることを前提として、再帰的なCTEを理解する必要があります。

編集:

以下は、任意の深さへの再帰クエリのコードです。私はこれを単に楽しみのためにまとめましたが、非常に効率的だとは思いません!

var maxDepth = 5;

var query = context.Locations.Where(o => o.ID == 1);
var nextLevelQuery = query;

for (var i = 0; i < maxDepth; i++)
{
    nextLevelQuery = nextLevelQuery.SelectMany(o => o.Children);
    query = query.Concat(nextLevelQuery);
}

フラット化されたリストは変数クエリ内にあります

4
Martin Booth

私のモデルにはChildrenプロップがなかったので、Nikhil Agrawalの答えはうまくいきません。ここに私の解決策を示します。

次のモデルで:

public class Foo
{
    public int Id { get; set; }
    public int? ParentId { get; set; }  
    // other props
}

以下を使用して、1つのアイテムの子を取得できます。

List<Foo> GetChildren(List<Foo> foos, int id)
{
    return foos
        .Where(x => x.ParentId == id)
        .Union(foos.Where(x => x.ParentId == id)
            .SelectMany(y => GetChildren(foos, y.Id))
        ).ToList();
}

例のために。

List<Foo> foos = new List<Foo>();

foos.Add(new Foo { Id = 1 });
foos.Add(new Foo { Id = 2, ParentId = 1 });
foos.Add(new Foo { Id = 3, ParentId = 2 });
foos.Add(new Foo { Id = 4 });

GetChild(foos, 1).Dump(); // will give you 2 and 3 (ids)
3
Mehdi Dehghani

再帰的にパブリック静的リストを使用してすべての子を追加するためのリストを作成しますList List = new List();

再帰的な機能

 static  void GetChild(int id) // Pass parent Id
                {

                    using (var ctx =  new CodingPracticeDataSourceEntities())
                    {
                        if (ctx.Trees.Any(x => x.ParentId == id))
                        {
                            var childList = ctx.Trees.Where(x => x.ParentId == id).ToList();
                            list.AddRange(childList);
                            foreach (var item in childList)
                            {
                                GetChild(item.Id);
                            }

                        }

                    }
                }

サンプルモデル

 public partial class Tree
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Nullable<int> ParentId { get; set; }
    }
1
Muhammed Afsal

LocationsDbSet<Location>あなたのDBコンテキストでは、これはあなたの問題を解決します。何か不足しているようですので、明確にしてください。

dbContext.Locations.ToList()
// IDs only would be dbContext.Locations.Select( l => l.ID ).ToList()
0
Moho