将所有子级放入一个列表 - 递归 C#

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19237868/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 14:28:59  来源:igfitidea点击:

Get All Children to One List - Recursive C#

c#linqentity-frameworkrecursion

提问by Will

C# | .NET 4.5 | Entity Framework 5

C# | .NET 4.5 | 实体框架 5

I have a class in Entity Framework that looks like this:

我在实体框架中有一个类,如下所示:

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

ID is the identifier of the location, ParentID links it to a parent, and Children contains all of the children locations of the parent location. I'm looking for some easy way, likely recursively, to get all "Location" and their children to one single List containing the Location.ID's. I'm having trouble conceptualizing this recursively. Any help is appreciated.

ID 是位置的标识符,ParentID 将其链接到父位置,Children 包含父位置的所有子位置。我正在寻找一些简单的方法,可能是递归的,将所有“位置”和他们的孩子放到一个包含 Location.ID 的列表中。我在递归地概念化这个问题上遇到了麻烦。任何帮助表示赞赏。

This is what I have so far, its an extension to the entity class, but I believe it could be done better/simpler:

这是我到目前为止所拥有的,它是实体类的扩展,但我相信它可以做得更好/更简单:

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();
}

UPDATED

更新

I ended up writing the recursion in SQL, throwing that in a SP, and then pulling that into Entity. Seemed cleaner and easier to me than using Linq, and judging by the comments Linq and Entity don't seem the best route to go. Thanks for all of the help!

我最终用 SQL 编写了递归,将其放入 SP,然后将其拉入实体。对我来说似乎比使用 Linq 更干净、更容易,从评论来看,Linq 和 Entity 似乎不是最好的路线。感谢所有的帮助!

采纳答案by Nikhil Agrawal

You can do SelectMany

你可以做SelectMany

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

You can use where condition for some selective results like

您可以将 where 条件用于某些选择性结果,例如

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

If you only required Id's of Children you can do

如果您只需要孩子的身,您可以这样做

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

回答by Moho

Assuming Locationsis a DbSet<Location>in your DB context, this will solve your problem "I'm looking for some easy way ... to get all 'Location' and their children to one single List containing the Location.ID's". Seems like I'm missing something, so please clarify if so.

假设LocationsDbSet<Location>在您的数据库上下文中,这将解决您的问题“我正在寻找一些简单的方法......将所有'位置'及其子项放到一个包含 Location.ID 的列表中”。好像我遗漏了一些东西,所以请澄清一下。

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

回答by Martin Booth

Entity framework does not currently support recursion, and for that reason you can either

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

  • Rely on lazy loading child collections as you have done (beware the N+1 problem)
  • Query an arbitrary depth of objects (This will be an ugly query, though you could generate it using System.Linq.Expressions)
  • 像你所做的那样依赖延迟加载子集合(注意 N+1 问题)
  • 查询任意深度的对象(这将是一个丑陋的查询,但您可以使用 System.Linq.Expressions 生成它)

The only real option would be to avoid using LINQ to express the query, and instead resort to standard SQL.

唯一真正的选择是避免使用 LINQ 来表达查询,而是求助于标准 SQL。

Entity framework supports this scenario fairly well whether you're using code first or not.

无论您是否首先使用代码,实体框架都可以很好地支持这种情况。

For code-first, consider something along the lines of

对于代码优先,请考虑以下方面的内容

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

For model-first, consider using a defining querywhich I think is a good option as it allows further composition, or stored procedures.

对于模型优先,考虑使用定义查询,我认为这是一个不错的选择,因为它允许进一步组合或存储过程。

To recursively get back data, you will need to understand recursive CTEs assuming you're using SQL Server, and that it is version 2005+

要递归取回数据,假设您使用的是 SQL Server,并且它是 2005+ 版本,您将需要了解递归 CTE

EDIT:

编辑:

Here is the code for a recursive query to an arbitrary depth. I put this together just for fun, I doubt it would be very efficient!

这是对任意深度的递归查询的代码。我把它放在一起只是为了好玩,我怀疑它会非常有效!

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);
}

The flattened list is in the variable query

扁平化列表在变量查询中

回答by Jonathas Costa

Try this Extension method:

试试这个扩展方法:

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);
}

And you can use it like this:

你可以像这样使用它:

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

回答by user1477388

I would like to contribute my own solution, which was modified from the references below:

我想贡献我自己的解决方案,该解决方案是从以下参考资料中修改而来的:

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;
}

Example:

例子:

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);

Output:

输出:

  • Dominic
  • Brittany
  • 多米尼克
  • 布列塔尼

Class:

班级:

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

Ref. https://stackoverflow.com/a/21054096/1477388

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

Note: Can't find the other reference, but someone else on SO published an answer that I copied some code from.

注意:找不到其他参考资料,但 SO 上的其他人发布了我从中复制了一些代码的答案。

回答by GreatAndPowerfulOz

This will do the trick:

这将解决问题:

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));
    }
}

Use it like this:

像这样使用它:

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();

回答by Mehdi Dehghani

I had no Childrenprop in my model, so Nikhil Agrawal's answer doesn't work for me, so here is my solution.

我的Children模型中没有道具,所以Nikhil Agrawal的答案对我不起作用,所以这是我的解决方案。

With following model:

使用以下型号:

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

You can get children of one item using:

您可以使用以下方法获取一项的子项:

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();
}

For ex.

例如。

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)

回答by Muhammed Afsal

Create list to add all child using recursively public static List list = new List();

创建列表以使用递归方式添加所有子项 public static List list = new List();

recursive funtion

递归函数

 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);
                            }

                        }

                    }
                }

Sample model

示例模型

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