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的List中。我在递归地概念化这个问题时遇到了麻烦。任何帮助表示赞赏。
这是我到目前为止所做的,它是实体类的扩展,但我相信它可以做得更好/更简单:
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,然后将其拉入实体。看起来更干净,比使用Linq更容易,并且根据评论判断Linq和Entity似乎不是最好的路线。感谢您的帮助!
答案 0 :(得分:14)
您可以执行SelectMany
List<Location> result = myLocationList.SelectMany(x => x.Children).ToList();
您可以将where条件用于某些选择性结果,例如
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();
答案 1 :(得分:11)
尝试此扩展方法:
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);
答案 2 :(得分:6)
这样可以解决问题:
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();
答案 3 :(得分:4)
实体框架目前不支持递归,因此您可以
唯一真正的选择是避免使用LINQ来表达查询,而是使用标准SQL。
无论您是否首先使用代码,实体框架都能很好地支持这种情况。
对于代码优先,请考虑
的内容var results = this.db.Database.SqlQuery<ResultType>(rawSqlQuery)
对于模型优先,考虑使用defining query,我认为这是一个不错的选择,因为它允许进一步的组合或存储过程。
要递归地获取数据,您需要了解递归CTE,假设您使用的是SQL Server,并且它是版本2005 +
编辑:
以下是对任意深度的递归查询的代码。我把它放在一起只是为了好玩,我怀疑它会非常有效!
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 :(得分:4)
我想提供自己的解决方案,该解决方案已从以下参考资料中修改:
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 :(得分:2)
我的模型中没有Children
道具,所以 Nikhil Agrawal 的回答对我不起作用,所以这是我的解决方案。
使用以下型号:
public class Foo
{
public int Id { get; set; }
public int? ParentId { get; set; }
// other props
}
您可以使用以下方式获得一个项目的孩子:
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)
答案 6 :(得分:1)
创建列表以递归添加所有子项 公共静态列表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; }
}
答案 7 :(得分:0)
假设Locations
在您的数据库环境中是DbSet<Location>
,这将解决您的问题“我正在寻找一些简单的方法......将所有'位置'和他们的孩子放到一个单一的位置包含Location.ID的列表“。好像我错过了什么,所以请澄清一下。
dbContext.Locations.ToList()
// IDs only would be dbContext.Locations.Select( l => l.ID ).ToList()
答案 8 :(得分:0)
这是我压平孩子的方法。
private Comment FlattenChildComments(Comment comment, ref Comment tempComment)
{
if (comment.ChildComments != null && comment.ChildComments.Any())
{
foreach (var childComment in comment.ChildComments)
{
tempComment.ChildComments.Add(childComment);
FlattenChildComments(childComment, ref tempComment);
}
}
comment.ChildComments = tempComment.ChildComments;
return comment;
}
答案 9 :(得分:0)
@NikhilAgrawal 接受的答案不会像@electricalbah 指出的那样递归地获取所有子孙。
我确实错过了在代码审查中给出的@EricLippert 的回答。
https://codereview.stackexchange.com/a/5661/96658
static IEnumerable<T> DepthFirstTreeTraversal<T>(T root, Func<T, IEnumerable<T>> children)
{
var stack = new Stack<T>();
stack.Push(root);
while(stack.Count != 0)
{
var current = stack.Pop();
// If you don't care about maintaining child order then remove the Reverse.
foreach(var child in children(current).Reverse())
stack.Push(child);
yield return current;
}
}
这样称呼:
static List<Location> AllChildren(Location start)
{
return DepthFirstTreeTraversal(start, c=>c.Children).ToList();
}
我在下面用 SelectMany
做了一个例子。正如您从 Immediate Window
中看到的,如果您使用该解决方案,您甚至不会获得父 ID。