Linq查询从树结构中选择项目,但查看整个深度

这是我制作的课程:

public class ItemTree { public Int32 id { get; set; } [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public String text { get; set; } [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public List item { get; set; } public int parentId { get; set; } } 

以下是我如何使用它:

 var tree = new ItemTree(); tree.id = 0; tree.text = "sometext"; tree.item = new List(); foreach (...) { if (tree.item.Count == 0) { tree.item.Add(new ItemTree { id = my_id, text = my_name, item = new List(), parentId = my_par }); } else { tree.item.Where(x => x.id == my_par) .Select(x => x.item) .First() .Add(new ItemTree { id = my_id, text = my_name, item = new List(), parentId = my_par }); } } 

它在Where子句的行中崩溃了。 它崩溃的原因是:树有一个项目列表,我的查询只检查树的第一项,而不是他的孩子。

如何在树的整个深度搜索并在那里添加项目?

将树结构展平为列表可能很方便。 如果您只有一个包含树的所有节点的IEnumerable ,那么某些逻辑将更容易表达。 您没有丢失任何信息,因为您仍然在每个节点上都有父ID。

这是一个自然递归的问题。 使用递归lambda,尝试类似于:

 Func> flattener = null; flattener = t => new[] { t } .Concat(t.item == null ? Enumerable.Empty() : t.item.SelectMany(child => flattener(child))); 

请注意,当您像这样制作递归Func ,必须首先单独声明Func ,并将其设置为null。

您还可以使用迭代器块方法展平列表:

 public static IEnumerable Flatten(ItemTree node) { yield return node; if (node.item != null) { foreach(var child in node.item) foreach(var descendant in Flatten(child)) yield return descendant; } } 

无论哪种方式,一旦树被展平,您可以在展平列表上执行简单的Linq查询以查找节点:

 flattener(tree).Where(t => t.id == my_id); 

然后,为了添加到树中,您可以执行以下操作:

 var itemOfInterest = flattenedTree.Where(t => t.id == myId).Single(); itemOfInterest.item = itemOfInterest.item ?? new List(); itemOfInterest.item.Add(myItemToAdd); 

使用我们的两种展平策略之一生成flattenedTree

我还想注意, item不是列表属性的好名字。 这些属性通常是多元化的( items )。 此外,属性通常是大写的( Items )。

这可能有所帮助:

 public static IEnumerable SelectRecursively(this IEnumerable e, Func> memberSelector) { foreach (T item in e) { yield return item; IEnumerable inner = memberSelector(item); if (inner != null) inner.SelectRecursively(memberSelector); } } 

使用方式如下:

 List tree = GetTree(); List flattenedTree = tree.SelectRecursively(T => T.Items).ToList(); 

这将启动递归选择(深度遍历),您可以在其中使用其他LinQfunction,例如.Where()

您正在使用First()而不是FirstOrDefault() 。 您应该执行以下操作。

 var item = tree.item.Where(x => x.id == my_par) .Select(x => x.item) .FirstOrDefault(); if (item != null) .Add(new ItemTree { id = my_id, text = my_name, item = new List(), parentId = my_par }); 
  1. 您应该在ItemTree中添加一个mehod HasId
  2. 该方法应实现特定Id的递归搜索并返回true或false的答案
  3. 使用(x => x.HasId(my_par))

你需要以某种方式递归树。 一种解决方案是为ItemTree对象创建一个迭代器,如:

 public class ItemTree { //simple DFS walk of the tree public IEnumerable GetChildren() { //no items, stop execution if ((item == null) || (item.Count == 0)) yield break; foreach (var child in item) { //return the child first yield return child; //no way to yield return a collection foreach (var grandchild in child.GetChildren()) { yield return grandchild; } } } } 

现在找到父母是微不足道的,类似于

 var parent = tree.GetChilden().First(c => c.id == my_par); parent.Add(new ItemTree { id = my_id, text = my_name, item = new List(), parentId = my_par }); 

从2013-06-13 12:14解决@AgentFire的问题必须扩展到

 public static IEnumerable SelectRecursively(this IEnumerable e, Func> memberSelector) { foreach (T item in e) { yield return item; IEnumerable inner = memberSelector(item); if (inner != null) { foreach(T innerItem in inner.SelectRecursively(memberSelector)) { yield return innerItem; } } } } 

将内部项目放入结果列表中。

感谢@AgentFire这个好主意。