【问题标题】:How to select from DataTable with join to same table?如何从连接到同一张表的 DataTable 中进行选择?
【发布时间】:2013-09-25 14:17:39
【问题描述】:

我有DataTable 对象,它包含一些“树数据结构”。数据不存储在任何数据库中,我只是使用DataTable来操作数据,无需SQL Server。

我的数据看起来像这样(缩进只是为了更好地阅读这里):

DataTable dtCategories = GetCategoriesAsDataTable();

id    name    parentId
int   string  int
----------------------
 1    One         0
 2      OneA      1
 3      OneB      1
 4    Two         0
 5      TwoA      4
 6      TwoB      4
 7        TwoAA   5
 8        TwoAB   5

到目前为止 - 我正在考虑使用“where parentId = 0”选择第一级并将其放在单独的 DataTable 中,如下所示:

DataTable dtFirstLevel = dtCategories.Select("[parentId] = 0");

// and after this - create DataTable for second level
// but I don't know how can I use "IN" clause here
DataTable dtSecondLevel = dtCategories.Select(?????????);
  1. 如何只选择树的前 2 层?
  2. 如何在没有 SQL Server 的情况下选择此项(仅使用数据对象)?

【问题讨论】:

  • 到目前为止你尝试过什么?您如何确定树的级别?除非您在数据与其行中的级别之间定义了关系(即缩进或 A [1] 与 AA [1-1] 与 AAB [1-1-2]),否则使用该选择属性对您毫无价值,因为有没有办法遍历一棵树。无论如何,您为什么要尝试使用 DataTable 对树结构进行建模?使用 SQL 这样做可能不是问题,但如果没有 WHERE 子句之类的东西,它会相当复杂。
  • 我刚刚添加了“到目前为止我尝试过的内容”,我想现在你可以弄清楚如何确定树的级别了。
  • foreach (var dt in dtFirstLevel){ var secondLevel = dtCategories.Select(string.Format("[parentId] = {0}", dt.Id)); 工作吗?
  • @DrewCopenhaver 所以我必须选择第一级(parentId=0)并迭代以获取 parentId 匹配的行?
  • 不应该 TwoATwoB4 作为 parentId?

标签: c# datatable


【解决方案1】:

也许这会有所帮助:

var rows = table.AsEnumerable();
var parents = rows.Where(r => !r.Field<int?>("parentId").HasValue);
var children = rows.Where(r => r.Field<int?>("parentId").HasValue);
var secondLevel = from parent in parents
                  join child in children
                  on parent.Field<int>("id") equals child.Field<int?>("parentId").Value
                  select child;
var both = parents.Concat(secondLevel).CopyToDataTable();

请注意,我使用 Nullable&lt;int&gt; 而不是 0 作为父级,因为这样更具可读性且不易出错。这是您的示例数据:

var table = new DataTable();
table.Columns.Add("id", typeof(int));
table.Columns.Add("name", typeof(string));
table.Columns.Add("parentId", typeof(int));
table.Rows.Add(1, "One", (int?)null);
table.Rows.Add(2, "OneA", 1);
table.Rows.Add(3, "OneB", 1);
table.Rows.Add(4, "Two", (int?)null);
table.Rows.Add(5, "TwoA", 4);
table.Rows.Add(6, "TwoB", 4);
table.Rows.Add(7, "TwoAA", 5);
table.Rows.Add(8, "TwoAB", 5);

结果:

1   One 
4   Two 
2   OneA    1
3   OneB    1
5   TwoA    4
6   TwoB    4

既然你想留在0而不是int?

var parents = rows.Where(r =>  r.Field<int>("parentId") == 0);
var children = rows.Where(r => r.Field<int>("parentId") != 0);
var secondLevel = from parent in parents
                  join child in children
                  on parent.Field<int>("id") equals child.Field<int>("parentId")
                  select child;

【讨论】:

  • 嗯,这正是我所要求的!但是我在 parentId 中没有空值(“主要”项目的 parentId = 0)。
  • @Kamil:您可以用== 0 替换可以为空的检查,但我更喜欢 null 而不是 0,因为它可能是一个有效的 ID。
  • @Kamil:请注意,我已经编辑了我的答案,因为我有一个讨厌的错字,我使用了行而不是父母。现在它只返回真正的父母和二级孩子。
  • 我看到有问题。我将使用 0 而不是 null parentId,因为数据来自非常稳定的 Web 服务,我预计该数据不会出现任何错误。可空的 int 会弄乱我的代码太多。
  • @Kamil:编辑了我的答案以提供0 没有可空值的方法。还注意到我的示例数据不是你的(复制/粘贴错字)。
【解决方案2】:

我认为这个功能可能会帮助您确定每个条目的树级别,以便您可以在选择中使用它:

    public int level(DataTable dt, DataRow row)
    {
        int parentid = int.Parse(row[2].ToString());
        if (parentid == 0)
            return 1;
        else
            return 1 + level(dt, GetDataRow(dt,parentid ));
    }

    public DataRow GetDataRow(DataTable dt, int id)
    {
        foreach (DataRow r in dt.Rows)
        {
            if (int.Parse(r[0].ToString()) == id) return r;
        }
        return null;
    }

【讨论】:

  • @Kamil,这基本上就是我要去的地方。如果您使用递归和计数器来获取父 id,您应该能够获得每个项目的级别。这样做的问题是,它可能比迭代慢得多,因为您必须对每个元素进行递归而不是循环遍历。我认为这是在正确的轨道上,但我不认为这是你想要使用的。现在,或者,如果您真的没有任何方法可以从除了 parentId 之外的数据中确定您所在的树的哪个级别,那么这可能是您唯一的解决方案。
  • @DrewCopenhaver 是的。当我看到这段代码时 - 我意识到在我的情况下这将非常低效(请参阅最后一条评论)。
  • @AliBaghdadi +1 获取有用的代码和想法,但我现在不会使用它。
  • @Kamil 迭代在您的情况下很有用,但是如果您稍后需要改变主意并想要选择 3 个级别怎么办?
  • @AliBaghdadi 没关系。这仍然是一个低效的解决方案。在这种特殊情况下,循环总是比递归更有效。
【解决方案3】:

您有几个选项可以解决您的问题。正如@Ali 所提议的,您可以像这样使用递归:

public int level(DataTable dt, DataRow row)
{
    int parentid = int.Parse(row[2].ToString());
    if (parentid == 0)
        return 1;
    else
        return 1 + level(dt, GetDataRow(dt,parentid ));
}

public DataRow GetDataRow(DataTable dt, int id)
{
    foreach (DataRow r in dt.Rows)
    {
        if (int.Parse(r[0].ToString()) == id) return r;
    }
    return null;
}

但问题是您最终会遍历每个元素,然后在每次迭代中使用递归。如果除了 parentId 之外,您的列与其在树中的级别之间绝对没有数据关系,那么这是您唯一的解决方案。

另一方面,如果你确实有一个关系,你有 name[level of tree] 像 Name[A] 是树级别 1,Name[AB] 是树级别 2 和正确的节点,然后迭代每个喜欢:

    foreach (DataRow r in dt.Rows)
    {
        //Pull out the element
        //Check the element's level
       //Add it to the result set if level <= 2
    }

我个人更喜欢通过实际构建树结构或使用 SQL WHERE 子句来解决问题,但很难证明时间的合理性。根据您从哪里获取这些数据,您还可以添加一个额外的列,根据插入的位置告诉您节点所在的级别。如果它有一个祖父节点(即两个父节点),则不要将其包含在结果集中。

【讨论】:

    【解决方案4】:
    DataTable level1 = (from t in dtCategories.AsEnumerable()
                        where t.Field<int>("parentId") == 0
                        select t).CopyToDataTable();
    
    DataTable level2 =(from t1 in dtCategories.AsEnumerable()
                            join t2 in dtCategories.AsEnumerable() 
                               on t1.Field<int>("id") equals t2.Field<int>("parentId")
                       where t1.Field<int>("parentId") == 0
                       select t2).CopyToDataTable();
    

    【讨论】:

      【解决方案5】:

      另一种方法是,这将为您提供一个包含关卡和行项目本身的新对象。这将适用于 n 个级别...

              var nodes = table.AsEnumerable();
      
              //var nodes = new List<TreeNode>();
      
              var parentId = 0;
              var countLevel = 0;
              var allNods = new List<dynamic>();
      
              while (nodes.Any(p => p.Field<int>("parentId") == parentId))// && countLevel < 2) 
                  // countlevel< 2 only to give you the first 2 levels only...
              {
                  var nodesWithLevel = nodes.Where(p => p.Field<int>("parentId") == parentId)
                              .Select(p => new { Level = parentId, Node = p });
      
                  allNods = allNods.Concat<dynamic>(nodesWithLevel).ToList();
                  parentId++;
                  countLevel++;
              }
      

      代码当前期望根节点的 parentId = 0。也可以更改为 null...

      【讨论】:

        猜你喜欢
        • 2015-02-07
        • 1970-01-01
        • 1970-01-01
        • 2018-08-17
        • 2021-03-03
        • 1970-01-01
        • 1970-01-01
        • 2011-04-21
        • 1970-01-01
        相关资源
        最近更新 更多