【问题标题】:Recursive Linq - Parent indication for child property? [closed]Recursive Linq - 子属性的父指示? [关闭]
【发布时间】:2015-03-15 15:04:52
【问题描述】:

我有一个表(比如本例中的 DataTable),其中包含层次结构记录(id、ParentId、IsEmergency):

ParentId=0 表示根。

所以我们有这个:

0
|
+--- 4706606
|
+--- 4706605
|
+--- 4666762
        |
        +--- 4668461
               |
               +--- 4706607 

我只显示那些有parentId=0的记录

但我想添加另一个逻辑列,其中指出“此父级在它的一个子级/子级中具有 IsEmergency=true

所以我来了

id         parentId        hasEmergencyInOneOfItsChild
-------------------------------------------------------
4706606    0                false
4706605    0                false
4666762    0                true

问题:

如何通过递归 linq 实现它?

为方便起见,我创建了这个数据表:

   DataTable dt = new DataTable("myTable");
   dt.Columns.Add("id", typeof (int));
   dt.Columns.Add("parentId", typeof (int));
   dt.Columns.Add("isEmergency", typeof (bool));

   DataRow row = dt.NewRow();
   row["id"] =4706606;
   row["parentId"] = 0;
   row["isEmergency"] =false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4706605;
   row["parentId"] = 0;
   row["isEmergency"] = false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4666762;
   row["parentId"] = 0;
   row["isEmergency"] =false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4668461;
   row["parentId"] = 4666762;
   row["isEmergency"] = false;
   dt.Rows.Add(row);

        row = dt.NewRow();
   row["id"] =4706607;
   row["parentId"] = 4668461;
   row["isEmergency"] = true;
   dt.Rows.Add(row);

我尝试了一些东西,但它很讨厌而且效率不高

【问题讨论】:

  • 您正在寻找将从哪里检索数据的 Linq 查询?实际的数据源是什么?
  • @haim770 不。我说的是内存中的 Linq,用于已经存在的数据源(内存中的数据表)。这里不涉及 SQL。
  • @haim770 这就像一个论坛。每个问题都有一个父问题。
  • “我尝试了一些东西,但它很讨厌而且效率不高”——你尝试了什么?它奏效了吗?它与您想要的具体方式有何不同?请提供a good, minimal, complete code example,展示您目前所获得的信息。

标签: c# linq recursion


【解决方案1】:

一种方法是定义一个包含相关属性的数据结构和一个递归属性以确定它是否在isEmergency层次结构中:

internal class Node
{
    internal string Id { get; set; }
    internal IEnumerable<Node> Children { get; set; } 
    internal bool IsEmergency { get; set; }

    internal bool IsEmergencyHierarchy
    {
        get { return IsEmergency || 
                     (Children != null && Children.Any(n => n.IsEmergencyHierarchy)); }
    }
}

然后你可以这样构造树:

// convert rows to nodes
var nodes = dt.Rows.Cast<DataRow>().Select(r => new Node
{
    Id = r["id"].ToString(),
    ParentId = r["parentId"].ToString(),
    IsEmergency = (r["isEmergency"] as bool? == true)
}).ToList();

// group and index by parent id
var grouped = nodes.GroupBy(n => n.ParentId).ToDictionary(g => g.Key);

// match up child nodes
foreach (var n in nodes)
{
    n.Children = grouped.ContainsKey(n.Id) 
        ? (IEnumerable<Node>)grouped[n.Id] 
        : new Node[0];
}

// select top nodes
var top = grouped.ContainsKey("0") 
    ? (IEnumerable<Node>)grouped["0"] 
    : new Node[0];

您可以像这样检查结果:

foreach (var t in top)
{
    Console.WriteLine("{0} - {1}", t.Id, t.IsEmergencyHierarchy);
}

输出:

4706606 - False
4706605 - False
4666762 - True

【讨论】:

    【解决方案2】:

    鉴于您的数据:

    var lookup = dt.Rows.Cast<DataRow>().Select(x => new
    {
        id = (int)x["id"],
        parentId = (int)x["parentId"],
        isEmergency = (bool)x["isEmergency"],
    }).ToLookup(x => x.parentId);
    

    我构建了一个由parentid“排序”的查找(假设它就像一个字典,但每个键都有多个值)

    // Taken from http://stackoverflow.com/a/21086662/613130
    public static IEnumerable<T> SelectSelfDescendents<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
    {
        foreach (var item in source)
        {
            yield return item;
    
            foreach (T item2 in SelectSelfDescendents(selector(item), selector))
                yield return item2;
        }
    }
    

    递归函数,递归返回输入集合和后代

    var roots = lookup[0].Select(x => new
    {
        x.id,
        // x.parentId // useless, it's 0
        isEmergency = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).Any(y => y.isEmergency),
        childs = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).ToArray()
    }).ToArray();
    

    一个小 Linq 来处理所有事情 :-)

    lookup[0]
    

    我们从“根”元素(parentId == 0 的元素)开始

    这个

    .SelectSelfDescendents(new[] { x }, y => lookup[y.id])
    

    给定一个 ienumerable 返回 ienumerable 加上它所有的递归子代。

    new[] { x }
    

    在单个元素的集合中转换“当前”根元素。

    y => lookup[y.id]
    

    这是一个后代选择器:给定一个元素,它会在查找中找到所有以 y.id 为父项的子项(因为查找是通过 parentId)

    所以最后SelectSelfDescendents 将返回new[] { x } 及其所有“后代”

    .Any(y => y.isEmergency)
    

    如果任何isEmergency 是true,则返回true

    现在,正如您所注意到的,我正在重新计算 SelectSelfDescendants 两次(一次用于 isEmergency,一次用于 childs)...事实上,childs 是我添加到“调试”递归的属性枚举,所以如果没有必要可以将其删除。如果你想保留它,你可以使用let 关键字(或“扩展”let 关键字,就像你在聊天中展示的那样......这是同样的事情)。请注意,我用.ToArray()“缓存”了SelectSelfDescendents(就像你做的那样)。没有它,它仍然会被计算两次,因为它返回一个IEnumerable&lt;&gt;

    var roots = (from x in lookup[0]
                 let childs = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).ToArray()
                 select new
                 {
                     x.id,
                     // x.parentId // useless, it's 0
                     isEmergency = childs.Any(y => y.isEmergency),
                     childs 
                 }).ToArray();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-07
      • 2015-10-27
      • 1970-01-01
      • 2010-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多