使用单个列表源在 linq 中创建树结构,父子作为对象的字符串

Create a tree structure in linq with a single list source with parent - child as strings of an object

我从包含以下数据的猫列表开始:

Name______________ Parent__________Description_______________FilesCoded________CodingReferences

"Blue"____________ "1.Colors"______"whatever"________________10________________11

"Red"_____________ "1.Colors"______"this"____________________2_________________3

"LightBlue"_______ "Blue"__________"that"____________________3_________________4

"Square"__________ "3.Forms"_______""________________________3_________________6

"ClearBlue"_______ "LightBlue"_____""________________________0_________________0

我需要创建这个输出:

1.Colors

____Blue

________LightBlue

____________ClearBlue

3.Forms

____Square

我使用这段代码,它可以解决问题,但我在树上有大约 15 到 20 个级别,这使得代码丑陋且效率极低。有没有办法做到这一点:

  1. 更简单,有一个循环遍历关卡的函数,而不是每次都复制代码;
  2. 更快。 table 非常大,因此每添加一个级别都会变慢。

当前代码:

@{
    var Cats = AsList(App.Data["Categories"]).OrderBy(o => o.Parent).ThenBy(oo => oo.Name);
    // List of objects with: Name, Parent, Description, and other fields.
    var FirstLevelTree = Cats.Where(a => Char.IsNumber(a.Name[0])).Select(s => s.Name).Distinct();
}

@functions{
    public CategoryInfo setCatInfo (string catName)
    {
        var Cats = AsList(App.Data["Categories"]);
        
        var returnInfo = new CategoryInfo();
        returnInfo.desc = "Error: empty string";
        returnInfo.info = "Error: empty string";
        
        var checkCat = Cats.Where(n => n.Name == catName);
        if (checkCat.Count() != 1)
        {
            returnInfo.info = "Error: no such cat in list";
            returnInfo.desc = "Error: no such cat in list";
        } else {
            var thisCat = checkCat.First();
            returnInfo.info = thisCat.Name + "somethingelse";
            returnInfo.desc = thisCat.Description + "alsosomethingelse";
        }
        return returnInfo;
    }
    
    public class CategoryInfo {
        public string info {get;set;}
        public string desc {get;set;}
    }
}

<div id="myCats">

    @foreach(var top in FirstLevelTree)
    {
        <p>@top</p>
        var setlvlOne = Cats.Where(a => a.Parent == top);
        foreach(var lvlOne in setlvlOne)
        {
            var getLvlOneInfo = setCatInfo(lvlOne.Name);
            <p style="margin-left: 20px;">@Html.Raw(getLvlOneInfo.info)</p>
               if (!String.IsNullOrEmpty(getLvlOneInfo.desc))
               {
                    <p style="margin-left: 30px;">@Html.Raw(getLvlOneInfo.desc)</p>
               }
            var setlvlTwo = Cats.Where(b => b.Parent == lvlOne.Name);
            foreach(var lvlTwo in setlvlTwo)
            {
                var getLvlTwoInfo = setCatInfo(lvlTwo.Name);
                <p style="margin-left: 40px;">@Html.Raw(getLvlTwoInfo.info)</p>
                   if (!String.IsNullOrEmpty(getLvlTwoInfo.desc))
                   {
                        <p style="margin-left: 50px;">@Html.Raw(getLvlTwoInfo.desc)</p>
                   }
                var setlvlThree = Cats.Where(c => c.Parent == lvlTwo.Name);
                foreach(var lvlThree in setlvlThree)
                {
                    var getLvlThreeInfo = setCatInfo(lvlThree.Name);
                    <p style="margin-left: 60px;">@Html.Raw(getLvlThreeInfo.info)</p>
                       if (!String.IsNullOrEmpty(getLvlThreeInfo.desc))
                       {
                            <p style="margin-left: 70px;">@Html.Raw(getLvlThreeInfo.desc)</p>
                       }
                    var setlvlFour = Cats.Where(d => d.Parent == lvlThree.Name);
                    foreach(var lvlFour in setlvlFour)
                    {
                        var getLvlFourInfo = setCatInfo(lvlFour.Name);
                         <p style="margin-left: 80px;">@Html.Raw(getLvlFourInfo.info)</p>
                           if (!String.IsNullOrEmpty(getLvlFourInfo.desc))
                           {
                                <p style="margin-left: 90px;">@Html.Raw(getLvlFourInfo.desc)</p>
                           }
                        var setlvlFive = Cats.Where(e => e.Parent == lvlFour.Name);
                        foreach(var lvlFive in setlvlFive)
                        {
                            var getLvlFiveInfo = setCatInfo(lvlFive.Name);
                            <p style="margin-left: 100px;">@Html.Raw(getLvlFiveInfo.info)</p>
                               if (!String.IsNullOrEmpty(getLvlFiveInfo.desc))
                               {
                                    <p style="margin-left: 110px;">@Html.Raw(getLvlFiveInfo.desc)</p>
                               }
                        }
                    }
                }
            }
        }  
    }
</div>

Simpler

是的。创建一个递归调用自身的 Razor Helper。这应该允许您避免示例中的所有重复代码,并且它将支持基本上无限的最大深度,而无需任何更多代码更改。

Faster

这取决于一直在做什么。

随着您获得更多关卡,大概您会生成更多 HTML。这将花费更多时间 server-side,并且还会使浏览器陷入困境。为避免此类问题,您可能需要只考虑加载用户真正感兴趣的部分。例如,您可以创建一个折叠结构,当用户向下钻取时,只从更深的节点加载数据。

我还会密切关注您未提供的代码中可能发生的情况。

  • setCatInfo 是一项昂贵的操作吗?
  • Cats collection 的后盾是什么?如果它使用延迟执行,例如 Entity Framework DbSet,您可能会在每次迭代 Cats.Where(...) 调用的结果时执行单独的 round-trip。

无论哪种方式,到处执行 Cats.Where() 都会给您的页面生成带来 O(n²) 算法复杂性。我建议将所有类别收集到一个查找中,按 Parent:

分组
var catsByParent = Cats.ToLookup(c => c.Parent);

这是一个 one-time O(log n) 操作,在那之后您应该能够更快地获得具有给定 parent 的类别。

var thisLevel = catsByParent[parentLevel.Name];

旁注

  • 使用 @Html.Raw() 是一种很大的代码味道。您很可能只想要 @thisLevel.desc。如果 @thisLevel.desc 可以保证直接注入您的 HTML 是安全的,它应该是 IHtmlString.
  • 与其将所有 HTML 元素放在同一层并使用样式嵌套它们,不如考虑使用嵌套的 HTML 结构和通用的 CSS 规则使每一层该嵌套结构的一部分比上一个结构缩进了一点。
@inherits ToSic.Sxc.Dnn.RazorComponent

@{
    var Cats = AsList(App.Data["Categories"]).OrderBy(o => o.Parent).ThenBy(oo => oo.Name);
    // List of objects with: Parent, Name, Description, FilesCoded, CodingReferences
    var CatsGrouped = Cats.ToLookup(a => a.Parent);
    var FirstLevelTree = Cats.Where(a => Char.IsNumber(a.Name[0])).Select(s => s.Name).Distinct();
}

<style>
.setmargins{
    padding-left: 2em;
}
</style>

@helper getChildren(string CatName, dynamic CatsGrouped)
{    
    var setChilds = CatsGrouped[CatName];
    <div class="setmargins">
    @foreach(var child in setChilds)
    {
        <div class="toogle">
            @child.Name (a: @child.FilesCoded urs: @child.CodingReferences)
            <div class="toogletarget setmargins">@(new HtmlString(child.Description))</div>
        </div>
        @getChildren(child.Name, CatsGrouped)
    }
    </div>
}

<div id="myCats" @Edit.TagToolbar(Content)>
    @foreach(var top in FirstLevelTree)
    {
        <div>@top</div>
        @getChildren(top, CatsGrouped)
    }
</div>

<script>
    $( ".toogletarget" ).toggle();
    $( ".toogle" ).click(function() {
        $(this).find('.toogletarget').toggle();
    });
</script>