首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >LINQ根据子顺序对平面列表进行排序

LINQ根据子顺序对平面列表进行排序
EN

Stack Overflow用户
提问于 2013-07-31 18:36:37
回答 5查看 5K关注 0票数 20

我目前正在尝试找出一种使用LINQ和C#对元素进行排序的好方法,但我有点做不到。

对于这个问题,让我们假设您有以下表

代码语言:javascript
复制
---TempTable
ID (int)
ParentID (int)
Name (varchar)
SortOrder (int)

ID和ParentID是相互关联的,它们给了我一种自我分层的数据结构。根元素在ID字段中为空。SortOrder只是整个表的一部分,并且基于ParentID,因此共享相同ParentID的元素在其中包含1、2、3。

让我们进一步假设以下数据:

代码语言:javascript
复制
ID = 1
ParentID = null
Name = Test 1
SortOrder = 1

ID = 2
ParentID = 1
Name = Test 2
SortOrder = 1

ID = 3
ParentID = 1
Name = Test 3
SortOrder = 2

ID = 4
ParentID = 2
Name = Test 4
SortOrder = 1

我想要的平面列表应该具有以下顺序:

代码语言:javascript
复制
Test 1 //root element with sort order 1 = very top
Test 2 //child element of root with sort order 1
Test 4 //child element of test 2 with sort order 1
Test 3 //child element of root with sort order 2

此外,我喜欢获取对象本身,而不是仅获取select new使用时抛出的一部分信息。

这是我失败的尝试之一:

代码语言:javascript
复制
from x in EntityModel.TempTables //DbSet<TempTable> by EntityFramework - which already holds all elements
   orderby x.SortOrder
   from y in x.TempTableChildren //Navigation Property by EntityFramework
   orderby y.SortOrder
   select y

提前感谢您的帮助。

编辑:

使用ParentID的顺序可能很有帮助,使用给定的TestData由于ID,ParentID是完美的顺序,但这不是真实的实时应用程序中的情况,因为它的数据驱动,某人可以删除一个条目,创建一个新的条目,并将其放在父条目下的特定顺序,您将得到如下所示:

代码语言:javascript
复制
ID = 193475037
ParentID = 2
Name = Test 192375937
SortOrder = 25

现在,在应用程序中,可以移动这一项,ParentID和SortOrder将随机更改为类似以下内容:

代码语言:javascript
复制
ID = 193475037
ParentID = 456798424
Name = Test 192375937
SortOrder = 4

为了更深入地解释这个问题,这里有一些代码-我如何在没有1个漂亮的Linq查询但有2个返回的情况下做到这一点:

代码语言:javascript
复制
public class LinqTestDemo
{
    Random rand = new Random();
    List<TempTable> list = new List<TempTable>();

    public List<TempTable> GetFlatData()
    {
        list = GetTestData();

        var rootElement = (from x in list
                            where x.ParentID == null
                            orderby x.SortOrder
                            select x).ToList();

        var flatList = OrderChilds(rootElement).ToList();

        foreach (var tempTable in flatList)
        {
            Console.WriteLine(string.Format("ID = {0} - ParentID = {1} - Name = {2} - SortOrder = {3}", tempTable.ID, tempTable.ParentID, tempTable.Name, tempTable.SortOrder));
        }

        return flatList;
    }

    private IEnumerable<TempTable> OrderChilds(List<TempTable> enumerable)
    {
        foreach (var tempTable in enumerable)
        {
            yield return tempTable;

            TempTable table = tempTable;
            var childs = OrderChilds((from x in list
                                        where x.ParentID == table.ID
                                        orderby x.SortOrder
                                        select x).ToList());

            foreach (var child in childs)
            {
                yield return child;
            }
        }
    }

    public List<TempTable> GetTestData()
    {
        var returnValue = new List<TempTable>();
        for (int i = 0; i < 50; i++)
        {
            var tempTable = new TempTable();
            tempTable.ID = i;
            if (i == 0)
                tempTable.ParentID = null;
            else
                tempTable.ParentID = rand.Next(0, i);

            var maxSortOrder = (from x in returnValue
                                where x.ParentID == tempTable.ParentID
                                select (int?)x.SortOrder).Max();

            if (maxSortOrder.HasValue)
                tempTable.SortOrder = maxSortOrder.Value + 1;
            else
                tempTable.SortOrder = 1;

            tempTable.Name = string.Format("Test {0:00}", i);
            returnValue.Add(tempTable);
        }

        return returnValue;
    }

    public class TempTable
    {
        public int ID { get; set; }
        public int? ParentID { get; set; }
        public string Name { get; set; }
        public int SortOrder { get; set; }
    }
}

@ Breadth-First vs Depth-First遍历:经过一些阅读后,我会说我想要的结果是深度优先遍历,其中处于相同级别深度的元素应该通过属性SortOrder进行排序。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-08-09 17:16:05

代码语言:javascript
复制
  public lEnumerable<TempTable> GetList( int? parentID = null){

     foreach ( var item in Context.TempTables
        .Where( x => x.ParentID == parentID )
        .OrderBy( x=> x.SortOrder)
        .ToList() {

        yield return item;

        foreach( var child in GetList( item.ID))
        {
            yield return child;
        }

     }
  }


  var sortedList = GetList();

它类似于你的方法,但它更小,更递归。并适用于多个深度级别。我更喜欢调用ToList,因为它会在查询下一个查询之前关闭结果集。

到目前为止,还没有办法在单个查询中做到这一点。

根据请求使用单个查询的

Entity Framework将自动填充所有子项。

代码语言:javascript
复制
 public IEnumerable<TempTable> PrepareList(IEnumerable<TempTable> list){
     list = list.OrderBy( x=> x.SortOrder);
     foreach(var item in list){
         yield return item;
         foreach(var child in PrepareList(item.ChildTempTables)){
             yield return child;
         }
     }
 }

 // since EF will automatically fill each children on fetch
 // all we need is just a top level nodes
 // which we will pass to PrepareList method
 var list = Context.TempTables.ToList().Where(x=> x.ParentID == null);
 var sortedList = PrepareList(list).ToList();

 // it is good to create list at the end if you are going to 
 // iterate it many times and logic will not change.
票数 14
EN

Stack Overflow用户

发布于 2013-08-09 17:23:55

这是一个非递归版本。它不会一遍又一遍地迭代初始列表。相反,它维护父子关系的字典,并将正在进行的预序树遍历的当前位置存储在枚举数中。

代码语言:javascript
复制
public static IEnumerable<TempTable> PreorderForest(IEnumerable<TempTable> list)
{
    var nodesByParent = list.GroupBy(x => x.ParentID.GetValueOrDefault(-1))
        .ToDictionary(xs => xs.Key, 
                      xs => xs.OrderBy(x => x.SortOrder).GetEnumerator());

    var stack = new Stack<IEnumerator<TempTable>>();
    stack.Push(nodesByParent[-1]);

    while (stack.Count > 0)
    {
        var nodes = stack.Peek();
        if (nodes.MoveNext())
        {
            yield return nodes.Current;
            IEnumerator<TempTable> children;
            if (nodesByParent.TryGetValue(nodes.Current.ID, out children))
                stack.Push(children);
        }
        else
            stack.Pop();
    }
}
票数 4
EN

Stack Overflow用户

发布于 2013-08-09 18:15:26

实际上,我不知道是否可以通过优雅的LINQ查询来实现。这是DFS的递归版本,它构建了查找来加快ParentID的搜索速度

代码语言:javascript
复制
public static IEnumerable<TempTable> SortedList(IEnumerable<TempTable> list = null, int? ParentID = null, ILookup<int?, TempTable> lookup = null)
{
    if (lookup == null)
        lookup = list.ToLookup(x => x.ParentID, x => x);

    foreach (var p in lookup[ParentID].OrderBy(x => x.SortOrder))
    {
        yield return p;
        foreach (var c in SortedList(lookup: lookup, ParentID: p.ID))
            yield return c;
    }
}
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17968069

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档