让所有孩子到一个列表 - 递归C#

时间:2013-10-08 02:03:55

标签: c# linq entity-framework recursion

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似乎不是最好的路线。感谢您的帮助!

10 个答案:

答案 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)

实体框架目前不支持递归,因此您可以

  • 依靠延迟加载子集合(请注意N + 1问题)
  • 查询任意深度的对象(这将是一个丑陋的查询,但您可以使用System.Linq.Expressions生成它)

唯一真正的选择是避免使用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。

enter image description here