3

我有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 的情况下选择它(仅使用数据对象)?
4

5 回答 5

3

也许这有帮助:

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<int>而不是 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;
于 2013-09-25T14:43:45.743 回答
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;
    }
于 2013-09-25T14:33:08.037 回答
1

你有几个选项来解决你的问题。正如@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 子句来解决问题,但很难证明时间的合理性。根据您从哪里获取这些数据,您还可以添加一个额外的列,根据插入的位置告诉您节点所在的级别。如果它有一个祖父节点(即两个父节点),则不要将它包含在结果集中。

于 2013-09-25T14:45:54.487 回答
1
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();
于 2013-09-25T14:48:27.700 回答
1

另一种方法是,这将为您提供一个包含级别和行项目本身的新对象。这将适用于 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 ......

于 2013-09-25T14:50:13.173 回答