【问题标题】:Read a sql hierarchy into a c# object将 sql 层次结构读入 c# 对象
【发布时间】:2012-02-08 07:36:07
【问题描述】:

您将如何读取 SQL 数据以获取单位的分层列表?

不依赖于仅 SQL Server 的解决方案?

public class Unit {
    public Unit Parent { get; set; }
    public int Id { get; set; }
    public String Name { get; set; }
}

List<Unit> list = new List<Unit>();

while(reader.Read())
{
    // read sql data into clr object UNIT
}

表格有 3 列:

Id| ParentId | Name
1 | Null     | bla
2 |   1      | x
3 |   1      | y
4 |   2      | z
5 |   2      | test

更新

That is the code which is taken from user marc_s:

 List<Unit> units = new List<Unit>();

            String commandText =
            @";WITH Hierarchy AS
              (
                 SELECT
                    ID,  ParentID = CAST(NULL AS INT),
                    Name, HierLevel = 1
                 FROM
                    dbo.Unit
                 WHERE
                    ParentID IS NULL

                 UNION ALL

                 SELECT
                    ht.ID, ht.ParentID, ht.Name, h1.HierLevel + 1
                 FROM
                    dbo.Unit ht
                 INNER JOIN 
                    Hierarchy h1 ON ht.ParentID = h1.ID
              )
              SELECT Id, ParentId, Name
              FROM Hierarchy
              ORDER BY HierLevel, Id";

            using(SqlConnection con = new SqlConnection(_connectionString))
            using (SqlCommand cmd = new SqlCommand(commandText, con))
            {
                con.Open();

                // use SqlDataReader to iterate over results
                using (SqlDataReader rdr = cmd.ExecuteReader())
                {
                    while (rdr.Read())
                    {
                        // get the info from the reader into the "Unit" object
                        Unit thisUnit = new Unit();

                        thisUnit.Id = Convert.ToInt32(rdr["Id"]);
                        thisUnit.UnitName = rdr["Name"].ToString();                     

                        // check if we have a parent
                        if (rdr["ParentId"] != DBNull.Value)
                        {
                            // get ParentId
                            int parentId = Convert.ToInt32(rdr["ParentId"]);

                            // find parent in list of units already loaded
                            // NOTE => not needed anymore => Unit parent = units.FirstOrDefault(u => u.Id == parentId);

                            // Instead use this method to find the parent:


                            Unit parent = FindParentUnit(units, parentId);

                            // if parent found - set this unit's parent to that object
                            if (parent != null)
                            {
                                thisUnit.Parent = parent;
                                parent.Children.Add(thisUnit);
                            }
                        }
                       else
                       {
                           units.Add(thisUnit);
                       }
                    }
                }
            }

            return units;

这是填充列表的屏幕截图

http://oi41.tinypic.com/rmpe8n.jpg

这是 Unit 表中的 sql 数据:

http://oi40.tinypic.com/mt12sh.jpg

问题

实际上填充的列表应该只有一个单元对象而不是 11(索引 0 - 10)。是的,列表中的第一个单元已正确填充,但索引 1 - 10 的单元不应出现在列表中。

实际上应该是这样的:

0
|--1
|   |--3
|   |   |--9
|   |   |--10  
|   |--4
|--2
|   |--5
|   |--6
|--7
|--8

更新和解决方案

private static Unit FindParentUnit(List<Unit> units, int parentId)
        {
            Unit parent;
            foreach (Unit u in units)
            {
                if (u.Id == parentId){
                    return u;                                    
                }
                parent = FindParentUnit(u.Children, parentId);
                if (parent != null)
                    return parent;
            }
            return null;
        } 

【问题讨论】:

  • 参见Wikipedia ON CTEDB2、Firebird [1]、Microsoft SQL Server、Oracle、PostgreSQL、HyperSQL 和 H2 支持公共表表达式(实验性)
  • 好的 - 那么问题是什么?您的“根”人 (ID=0) 有 四个孩子 - 正如您提供的数据所预期的那样。我敢肯定,如果您深入了解该根人员的“子项”集合,您也会找到其他节点及其子节点。
  • 如果您不想将所有节点都放入列表中 - 就不要这样做! :-) 所以在if (rdr["ParentId"] != DBNull.Value) 之后,有一个else { .... } 子句并将语句units.Add(thisUnit); 放入那个else 子句 - 然后只有那些没有父单元的单元将被卡在列表中。 ..相应地更新了我的答案.....
  • 单位父=units.FirstOrDefault(u => u.Id == parentId); Id 3 或 Id 4 的父级是 ParentId 1,它不在根级别的单位列表中,而是在 ID 为 1 的单位的 children 属性中。可能是 Linq SelectMany 帮助这不会是高性能的......是的,你的列表有 11 个单元对象的原始版本应该与 units.FirstOrDefault 搜索一起使用,但我只想要列表中的一个单元,因为它的层次结构绑定到一个控件,看到所有单元看起来很傻......

标签: c# sql tree hierarchy


【解决方案1】:

一种方法是使用对象关系映射器,例如实体框架来为您完成工作。 This answer 类似的 EF 问题应该会为您指明正确的方向。

【讨论】:

    【解决方案2】:

    应该这样做:-)

    // set up connection string
    string connectionString = "server=.;database=test;integrated Security=SSPI;";
    
    // define a CTE (Common Table Expression) to recursively build your hierarchical
    // structure into a flat list and order it according to its "sequence" (root first)
    string cteStatement =
                @";WITH Hierarchy AS
                  (
                     SELECT
                        ID,  ParentID = CAST(NULL AS INT),
                        Name, HierLevel = 1
                     FROM
                        dbo.HierarchyTest   -- replace with your table name!
                     WHERE
                        ParentID IS NULL
    
                     UNION ALL
    
                     SELECT
                        ht.ID, ht.ParentID, ht.Name, h1.HierLevel + 1
                     FROM
                        dbo.HierarchyTest ht   -- replace with your table name!
                     INNER JOIN 
                        Hierarchy h1 ON ht.ParentID = h1.ID
                  )
                  SELECT Id, ParentId, Name
                  FROM Hierarchy
                  ORDER BY HierLevel, Id";
    
    // set up list of "Unit" objects
    List<Unit> units = new List<Unit>();
    
    // create connection and command to query             
    using(SqlConnection conn = new SqlConnection(connectionString))
    using(SqlCommand cmd = new SqlCommand(cteStatement, conn))
    {
        conn.Open();
    
        // use SqlDataReader to iterate over results
        using(SqlDataReader rdr = cmd.ExecuteReader())
        {
            while(rdr.Read())
            {
                // get the info from the reader into the "Unit" object
                Unit thisUnit = new Unit();
    
                thisUnit.Id = rdr.GetInt32(0);
                thisUnit.Name = rdr.GetString(2);
                thisUnit.Children = new List<Unit>();
    
                // check if we have a parent
                if(!rdr.IsDBNull(1))
                {
                    // get ParentId
                    int parentId = rdr.GetInt32(1);
    
                    // find parent in list of units already loaded
                    Unit parent = units.FirstOrDefault(u => u.Id == parentId);
    
                    // if parent found - set this unit's parent to that object
                    if(parent != null)
                    {
                        thisUnit.Parent = parent;
                        parent.Children.Add(thisUnit);
                    }
                }
                else
                {
                    units.Add(thisUnit);
                }
            }
        }
    
        conn.Close();
    }
    

    这对你有用吗?

    CTE(通用表表达式)递归地扫描您的表并建立分层节点列表 - 通过按“层次结构级别”对其进行排序,您可以确保在其子节点出现之前获取所有父节点(以便代码工作)

    更新: 好的,看来您想将 only 节点与 no parent 放入结果列表中 - 这很好(但您没有'不是真的说你想要那样!!) - 我更新了上面的代码 - 请重新检查!

    【讨论】:

    • 我编辑了我的问题:请 ms sql server 独立解决方案 :)
    • @Pascal: CTE ANSI SQL-99 标准的一部分 - 它不是 SQL Server 特定的!
    • 你在 USING 语句中做了 conn.Close() 吗?
    • @Pascal:是的——它不疼——这样,我想做什么就很清楚了。我喜欢明确而不是依赖“魔法”效果......(这些效果非常适合备份 - 但不要开始依赖它们......)
    • 是的。如果节点的 id 由 sql 标识列设置,则节点将位于错误的位置。谢谢。
    【解决方案3】:

    您的数据代表一个树结构,您只需要构建树创建根单元并添加叶子。您可以使用字典而不是列表在树中搜索,这很简单。 这是一个仅在行按 id 升序排序时才有效的示例:

    Dictionary<Int32,Unit> dic = new Dictionary<Int32,Unit>();
    
    while(reader.Read()) 
    { 
        //create the new Unit
        // if the parent is not null get the parent unit from dic
        // add the new Unit to dic
    } 
    

    【讨论】:

    • 你有点不清楚。这是你的意思吗?单位单位=新单位(); If(!DB.IsNull(reader[„parentId“])){ Unit parent = dic[ Convert.ToInt32(reader[„parentId“]) ]; Dic.Add(unit.Id,unit); }
    猜你喜欢
    • 2011-09-23
    • 1970-01-01
    • 1970-01-01
    • 2012-09-27
    • 2022-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多