使用单个列表源在 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 个级别,这使得代码丑陋且效率极低。有没有办法做到这一点:
- 更简单,有一个循环遍历关卡的函数,而不是每次都复制代码;
- 更快。 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>
我从包含以下数据的猫列表开始:
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 个级别,这使得代码丑陋且效率极低。有没有办法做到这一点:
- 更简单,有一个循环遍历关卡的函数,而不是每次都复制代码;
- 更快。 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>