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のコメントから判断すると、最適なルートとは思えません。助けてくれてありがとう!
あなたがすることができます 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();
この拡張メソッドを試してください:
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);
これはトリックを行います:
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();
以下の参考文献から変更された独自のソリューションを提供したいと思います。
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=の別の誰かが、コードをコピーした回答を公開しました。
エンティティフレームワークは現在再帰をサポートしていません。そのため、次のいずれかを行うことができます。
唯一の実際のオプションは、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);
}
フラット化されたリストは変数クエリ内にあります
私のモデルには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)
再帰的にパブリック静的リストを使用してすべての子を追加するためのリストを作成します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; }
}
Locations
はDbSet<Location>
あなたのDBコンテキストでは、これはあなたの問題を解決します。何か不足しているようですので、明確にしてください。
dbContext.Locations.ToList()
// IDs only would be dbContext.Locations.Select( l => l.ID ).ToList()