SQL数据层次结构

Wil*_*ill 10 c# sql asp.net tree hierarchy

我已经查看了一些SQL层次结构教程,但它们对我的应用程序都没有多大意义.也许我只是没有正确理解它们.我正在编写一个C#ASP.NET应用程序,我想从SQL数据创建一个树视图层次结构.

这是层次结构的工作方式:

SQL TABLE

ID     | Location ID | Name
_______| __________  |_____________
1331   | 1331        | House
1321   | 1331        | Room
2141   | 1321        | Bed
1251   | 2231        | Gym

如果ID和位置ID相同,这将决定顶级父级.该父母的任何子女都将拥有与父母相同的位置ID.该孩子的任何孙子女的位置ID都等于孩子的ID,依此类推.

对于上面的例子:

- House
   -- Room
       --- Bed

任何帮助或指导易于遵循的教程将不胜感激.

编辑:

我到目前为止的代码,但只有父母和孩子,没有GrandChildren.我似乎无法弄清楚如何让它以递归方式获取所有节点.

using System;
using System.Data;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Configuration;
using System.Data.SqlClient;

namespace TreeViewProject
{
public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        PopulateTree(SampleTreeView);

    }



    public void PopulateTree(Control ctl)
    {

        // Data Connection
        SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["AssetWhereConnectionString1"].ConnectionString);
        connection.Open();

        // SQL Commands
        string getLocations = "SELECT ID, LocationID, Name FROM dbo.Locations";
        SqlDataAdapter adapter = new SqlDataAdapter(getLocations, connection);
        DataTable locations = new DataTable();
        // Fill Data Table with SQL Locations Table
        adapter.Fill(locations);
        // Setup a row index
        DataRow[] myRows;
        myRows = locations.Select();

        // Create an instance of the tree
        TreeView t1 = new TreeView();
        // Assign the tree to the control
        t1 = (TreeView)ctl;
        // Clear any exisiting nodes
        t1.Nodes.Clear();

        // BUILD THE TREE!
        for (int p = 0; p < myRows.Length; p++)
        {
            // Get Parent Node
            if ((Guid)myRows[p]["ID"] == (Guid)myRows[p]["LocationID"])
            {
                // Create Parent Node
                TreeNode parentNode = new TreeNode();
                parentNode.Text = (string)myRows[p]["Name"];
                t1.Nodes.Add(parentNode);

                // Get Child Node
                for (int c = 0; c < myRows.Length; c++)
                {
                    if ((Guid)myRows[p]["LocationID"] == (Guid)myRows[c]["LocationID"] 
                        && (Guid)myRows[p]["LocationID"] != (Guid)myRows[c]["ID"] /* Exclude Parent */)
                    {
                        // Create Child Node
                        TreeNode childNode = new TreeNode();
                        childNode.Text = (string)myRows[c]["Name"];
                        parentNode.ChildNodes.Add(childNode);
                    }
                }
            }
        }
        // ALL DONE BUILDING!

        // Close the Data Connection
        connection.Close();
    }

}
}
Run Code Online (Sandbox Code Playgroud)

这是来自实际SQL表的snippit:Locations

ID                                      LocationID                              Name
____________________________________    ____________________________________    ______________
DEAF3FFF-FD33-4ECF-910B-1B07DF192074    48700BC6-D422-4B26-B123-31A7CB704B97    Drop F
48700BC6-D422-4B26-B123-31A7CB704B97    7EBDF61C-3425-46DB-A4D5-686E91FD0832    Olway
06B49351-6D18-4595-8228-356253CF45FF    6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0    Drop E 5
E98BC1F6-4BAE-4022-86A5-43BBEE2BA6CD    DEAF3FFF-FD33-4ECF-910B-1B07DF192074    Drop F 6
F6A2CF99-F708-4C61-8154-4C04A38ADDC6    7EBDF61C-3425-46DB-A4D5-686E91FD0832    Pree
0EC89A67-D74A-4A3B-8E03-4E7AAAFEBE51    6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0    Drop E 4
35540B7A-62F9-487F-B65B-4EA5F42AD88A    48700BC6-D422-4B26-B123-31A7CB704B97    Olway Breakdown
5000AB9D-EB95-48E3-B5C0-547F5DA06FC6    6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0    Out 1
53CDD540-19BC-4BC2-8612-5C0663B7FDA5    6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0    Drop E 3
7EBDF61C-3425-46DB-A4D5-686E91FD0821    B46C7305-18B1-4499-9E1C-7B6FDE786CD6    TEST 1
7EBDF61C-3425-46DB-A4D5-686E91FD0832    7EBDF61C-3425-46DB-A4D5-686E91FD0832    HMN

谢谢.

Chr*_*sen 15

您正在寻找使用公用表表达式的递归查询,或简称CTE.可以在MSDN找到 SQL Server 2008中对此的详细说明.

通常,它们具有类似于以下的结构:

WITH cte_name ( column_name [,...n] )
AS (
    –- Anchor
    CTE_query_definition

    UNION ALL

    –- Recursive portion
    CTE_query_definition
)
-- Statement using the CTE
SELECT * FROM cte_name
Run Code Online (Sandbox Code Playgroud)

执行此操作时,SQL Server将执行类似于以下操作(从MSDN转换为更简单的语言):

  1. 将CTE表达式拆分为锚点和递归成员.
  2. 运行锚点,创建第一个结果集.
  3. 运行递归部分,将前一步作为输入.
  4. 重复步骤3,直到返回空集.
  5. 返回结果集.这是一个UNION ALL的锚点和所有递归步骤.

对于这个具体的例子,尝试这样的事情:

With hierarchy (id, [location id], name, depth)
As (
    -- selects the "root" level items.
    Select ID, [LocationID], Name, 1 As depth
    From dbo.Locations
    Where ID = [LocationID]

    Union All

    -- selects the descendant items.
    Select child.id, child.[LocationID], child.name,
        parent.depth + 1 As depth
    From dbo.Locations As child
    Inner Join hierarchy As parent
        On child.[LocationID] = parent.ID
    Where child.ID != parent.[Location ID])
-- invokes the above expression.
Select *
From hierarchy
Run Code Online (Sandbox Code Playgroud)

根据您的示例数据,您应该得到以下内容:

ID     | Location ID | Name  | Depth
_______| __________  |______ | _____
1331   | 1331        | House |     1
1321   | 1331        | Room  |     2
2141   | 1321        | Bed   |     3
Run Code Online (Sandbox Code Playgroud)

请注意,"健身房"不包括在内.根据您的示例数据,它的ID与[位置ID]不匹配,因此它不是根级别的项目.它的位置ID 2231未出现在有效父ID列表中.


编辑1:

您已经询问过如何将其转换为C#数据结构.在C#中表示层次结构的方法有很多种.这是一个例子,因其简单而选择.毫无疑问,真正的代码示例会更广泛.

第一步是定义层次结构中每个节点的外观.除了包含节点中的每个数据的特性,我已经包含ParentChildren性能,再加上方法来Add一个孩子和Get一个孩子.该Get方法将搜索节点的整个后代轴,而不仅仅是节点自己的子节点.

public class LocationNode {
    public LocationNode Parent { get; set; }
    public List<LocationNode> Children = new List<LocationNode>();

    public int ID { get; set; }
    public int LocationID { get; set; }
    public string Name { get; set; }

    public void Add(LocationNode child) {
        child.Parent = this;
        this.Children.Add(child);
    }

    public LocationNode Get(int id) {
        LocationNode result;
        foreach (LocationNode child in this.Children) {
            if (child.ID == id) {
                return child;
            }
            result = child.Get(id);
            if (result != null) {
                return result;
            }
        }
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你想要填充你的树.这里有一个问题:以错误的顺序填充树很困难.在添加子节点之前,您确实需要对父节点的引用.如果必须按顺序执行此操作,则可以通过两次传递(一个用于创建所有节点,另一个用于创建树)来缓解问题.但是,在这种情况下,这是不必要的.

如果您使用上面提供的SQL查询并按depth列排序,则可以在数学上确定在遇到其父节点之前永远不会遇到子节点.因此,您可以一次完成此操作.

您仍然需要一个节点作为树的"根".您可以决定这是否为"House"(来自您的示例),或者它是否是您为此目的创建的虚构占位符节点.我建议后来.

那么,代码!同样,这是为了简化和可读性而优化的.您可能希望在生产代码中解决一些性能问题(例如,不必经常查找"父"节点).我在这里避免了这些优化,因为它们增加了复杂性.

// Create the root of the tree.
LocationNode root = new LocationNode();

using (SqlCommand cmd = new SqlCommand()) {
    cmd.Connection = conn; // your connection object, not shown here.
    cmd.CommandText = "The above query, ordered by [Depth] ascending";
    cmd.CommandType = CommandType.Text;
    using (SqlDataReader rs = cmd.ExecuteReader()) {
        while (rs.Read()) {
            int id = rs.GetInt32(0); // ID column
            var parent = root.Get(id) ?? root;
            parent.Add(new LocationNode {
                ID = id,
                LocationID = rs.GetInt32(1),
                Name = rs.GetString(2)
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当当!该rootLocationNode现在包含您的整个层次.顺便说一下,我还没有真正执行过这段代码,所以如果你发现任何明显的问题,请告诉我.


编辑2

要修复示例代码,请进行以下更改:

删除此行:

// Create an instance of the tree
TreeView t1 = new TreeView();
Run Code Online (Sandbox Code Playgroud)

这一行实际上不是问题,但应删除.你在这里的评论是不准确的; 你并没有真正为控件分配树.相反,您正在创建一个新的TreeView,将其分配给t1,然后立即分配一个不同的对象t1.下一行执行后,您创建的TreeView将丢失.

修复您的SQL语句

// SQL Commands
string getLocations = "SELECT ID, LocationID, Name FROM dbo.Locations";
Run Code Online (Sandbox Code Playgroud)

使用ORDER BY子句将此SQL语句替换为我之前建议的SQL语句.阅读我之前的编辑,解释为什么"深度"很重要:您确实想要按特定顺序添加节点.在拥有父节点之前,无法添加子节点.

或者,我认为您不需要这里的SqlDataAdapter和DataTable的开销.我最初建议的DataReader解决方案更简单,更易于使用,并且在资源方面更有效.

此外,大多数C#SQL对象都会实现IDisposable,因此您需要确保正确使用它们.如果有什么实现IDisposable,请确保将其包装在using语句中(请参阅我之前的C#代码示例).

修复树木构建循环

您只获取父节点和子节点,因为您有父节点循环和子节点内循环.正如你必须已经知道的那样,你没有得到孙子孙女,因为你没有添加它们的代码.

你可以添加一个内在循环来获得孙子孙女,但显然你是在寻求帮助,因为你已经意识到这样做只会导致疯狂.如果你想要曾孙子会怎么样?内 - 内 - 内环?这种技术不可行.

你可能想过这里的递归.这是一个完美的地方,如果你正在处理树状结构,它最终会出现.既然您已经编辑了问题,很明显您的问题几乎与SQL无关.你的真正问题在于递归.有人可能会最终出现并为此设计一个递归解决方案.这将是一个完全有效的,可能更好的方法.

但是,我的答案已经涵盖了递归部分 - 它只是将其移动到SQL层.因此,我将保留以前的代码,因为我觉得这是一个合适的通用答案.根据您的具体情况,您需要进行一些修改.

首先,你不需要LocationNode我建议的课程.你正在使用TreeNode,这将工作正常.

其次,TreeView.FindNode它类似于LocationNode.Get我建议的方法,除了FindNode需要到节点的完整路径.要使用FindNode,您必须修改SQL以提供此信息.

因此,您的整个PopulateTree函数应如下所示:

public void PopulateTree(TreeView t1) {

    // Clear any exisiting nodes
    t1.Nodes.Clear();

    using (SqlConnection connection = new SqlConnection()) {
        connection.ConnectionString = "((replace this string))";
        connection.Open();

        string getLocations = @"
            With hierarchy (id, [location id], name, depth, [path])
            As (

                Select ID, [LocationID], Name, 1 As depth,
                    Cast(Null as varChar(max)) As [path]
                From dbo.Locations
                Where ID = [LocationID]

                Union All

                Select child.id, child.[LocationID], child.name,
                    parent.depth + 1 As depth,
                    IsNull(
                        parent.[path] + '/' + Cast(parent.id As varChar(max)),
                        Cast(parent.id As varChar(max))
                    ) As [path]
                From dbo.Locations As child
                Inner Join hierarchy As parent
                    On child.[LocationID] = parent.ID
                Where child.ID != parent.[Location ID])

            Select *
            From hierarchy
            Order By [depth] Asc";

        using (SqlCommand cmd = new SqlCommand(getLocations, connection)) {
            cmd.CommandType = CommandType.Text;
            using (SqlDataReader rs = cmd.ExecuteReader()) {
                while (rs.Read()) {
                    // I guess you actually have GUIDs here, huh?
                    int id = rs.GetInt32(0);
                    int locationID = rs.GetInt32(1);
                    TreeNode node = new TreeNode();
                    node.Text = rs.GetString(2);
                    node.Value = id.ToString();

                    if (id == locationID) {
                        t1.Nodes.Add(node);
                    } else {
                        t1.FindNode(rs.GetString(4)).ChildNodes.Add(node);
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您发现任何其他错误,请告诉我们!