如何从SQL查询构建对象层次结构? (对于WPF TreeView)

时间:2012-04-24 14:42:15

标签: sql wpf treeview hierarchical-data hierarchyid

感谢您抽出时间阅读这篇文章。

从SQL数据库获取数据时,我无法尝试构建层次结构对象。 请注意,我是一个新手程序员。

如何构建具有未知级别的层次结构对象?当我说未知级别时,我的意思是,每个节点可能有不同数量的子节点,而这些子节点可能拥有不同数量的子节点,依此类推。

我的想法是我需要使用我的SQL数据创建一个层次结构对象来绑定到WPF TreeView控件。

下面我已经包含了我到目前为止的代码。 代码的第一位是我的属性组成的类。请注意,“Products”类具有引用自身的ObservableCollection。我认为这是构建嵌套节点的方式。即列表中的列表。

第二段代码是我的Get方法,用于从SQL数据库下载数据。以下是我需要将下载的数据排序为层次结构的方法。

产品类别(属性)

public class Products : INotifyPropertyChanged, IDataErrorInfo
{
    private Int64 m_ID;
    private SqlHierarchyId m_Hierarchy;
    private string m_Name;
    private ObservableCollection<Products> m_ChildProducts;

    // Default Constructor
    public Products()
    {
        ChildProducts = new ObservableCollection<Products>();
    }

    //Properties

    public Int64 ID
    {
        get
        {
            return m_ID;
        }
        set
        {
            m_ID = value;
            OnPropertyChanged(new PropertyChangedEventArgs("ID"));
        }
    }

    public SqlHierarchyId Hierarchy
    {
        get
        {
            return m_Hierarchy;
        }
        set
        {
            m_Hierarchy = value;
            OnPropertyChanged(new PropertyChangedEventArgs("Hierarchy"));
        }
    }

    public String Name
    {
        get
        {
            return m_Name;
        }
        set
        {
            m_Name = value;
            OnPropertyChanged(new PropertyChangedEventArgs("Name"));
        }
    }

    public Int16 Level
    {
        get
        {
            return m_Level;
        }
        set
        {
            m_Level = value;
            OnPropertyChanged(new PropertyChangedEventArgs("Level"));
        }
    }

    public Int64 ParentID
    {
        get
        {
            return m_ParentID;
        }
        set
        {
            m_ParentID = value;
            OnPropertyChanged(new PropertyChangedEventArgs("ParentID"));
        }
    }

    public ObservableCollection<Products> ChildProducts
    {
        get
        {
            return m_ChildProducts;
        }
        set
        {
            m_ChildProducts = value;
            OnPropertyChanged(new PropertyChangedEventArgs("ChildProducts"));
        }
    }

    //INotifyPropertyChanged Event
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, e);
    }
}

从SQL DB获取数据的方法:

public static ObservableCollection<Products> GetProductsHierarchy()
    {

        ObservableCollection<Products> productsHierarchy = new ObservableCollection<Products>();

        SqlConnection connection = new SqlConnection(DBConnection.GetConnection().ConnectionString);

        string selectStatement = "SELECT ID, Hierarchy, Name, Hierarchy.GetLevel() AS Level, Hierarchy.GetAncestor(1) AS ParentHierarchy, " +
                                                 "(SELECT ID " +
                                                 "FROM SpecProducts " +
                                                 "WHERE (Hierarchy = SpecProducts_1.Hierarchy.GetAncestor(1))) AS ParentID " +
                                 "FROM  SpecProducts AS SpecProducts_1 " +
                                 "WHERE (EnableDisable IS NULL) " +
                                 "ORDER BY Hierarchy";

        SqlCommand selectCommand = new SqlCommand(selectStatement, connection);

        try
        {
            connection.Open();
            SqlDataReader reader = selectCommand.ExecuteReader();

            while (reader.Read())
            {

                Products product = new Products();
                product.ID = (Int64)reader["ID"];
                product.Name = reader["Name"].ToString();
                product.Hierarchy = (SqlHierarchyId)reader["Hierarchy"];
                product.Level = (Int16)reader["Level"];
                if (reader["ParentID"] != DBNull.Value)
                {
                    product.ParentID = (Int64)reader["ParentID"];
                }
                else
                {
                    product.ParentID = 0;
                }

                productsHierarchy.Add(product);

                // *** HOW TO BUILD HIERARCHY OBJECT WITH UNKNOWN & VARYING LEVELS?
                // *** ADD PRODUCT TO CHILDPRODUCT
            }

            return productsHierarchy;
        }
        catch (SqlException ex)
        {
            throw ex;
        }
        finally
        {
            connection.Close();
        }
    }

下面我附上了一张显示我的SQL查询数据结构的图片。 请注意,将来添加更多产品时,层次结构级别可能会更深。无论节点级别是多少,我需要创建的Hierarchy对象应该足够灵活,可以扩展。

非常感谢你的时间,非常感谢所有的帮助。

SQL Query Data

********* EDIT 26/04/2012 14:37 *******************

请在下面找到下载我的项目代码的链接(这只包含树视图代码)。 有人可以看看它,看看为什么我不能创建2级以上的节点?

代码由用户HB MAAM提供给我。感谢“HB MAAM”为您提供帮助!

Click this link to download code

3 个答案:

答案 0 :(得分:3)

我会为你创造一个例子,
1-首先我将创建一个包含来自DB

的数据的类
public class SqlDataDto
{
    public int? Id { get; set; }
    public int? ParentId { get; set; }

    public String Name { get; set; }
    public String OtherDataRelatedToTheNode { get; set; }
}

2-数据将转换为分层数据,我们将使用此类来保存数据:

public class LocalData : INotifyPropertyChanged
{
    private int? _id;
    public int? Id
    {
        get { return _id; }
        set { _id = value; OnPropertyChanged("Id"); }
    }

    private int? _parentId;
    public int? ParentId
    {
        get { return _parentId; }
        set { _parentId = value; OnPropertyChanged("ParentId"); }
    }

    private string _name;
    public String Name
    {
        get { return _name; }
        set { _name = value; OnPropertyChanged("Name"); }
    }

    private string _otherDataRelatedToTheNode;
    public String OtherDataRelatedToTheNode
    {
        get { return _otherDataRelatedToTheNode; }
        set { _otherDataRelatedToTheNode = value; OnPropertyChanged("OtherDataRelatedToTheNode"); }
    }

    private LocalData _parent;
    public LocalData Parent
    {
        get { return _parent; }
        set { _parent = value; OnPropertyChanged("Parent"); }
    }

    private ObservableCollection<LocalData> _children;
    public ObservableCollection<LocalData> Children
    {
        get { return _children; }
        set { _children = value; OnPropertyChanged("Children"); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(String propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
        }
    }
}     

3-最后我们需要将sql数据更改为分层数据:

public List<LocalData> GetHerachy(List<SqlDataDto> sqlData)
{
    var sqlParents = sqlData.Where(q => q.ParentId == null).ToList();
    var parents = sqlParents.Select(q => new LocalData {Id = q.Id, Name = q.Name}).ToList();
    foreach (var parent in parents)
    {
        var childs = sqlData.Where(q => q.ParentId == parent.Id).Select(q => new LocalData { Id = q.Id, Name = q.Name , Parent = parent});
        parent.Children = new ObservableCollection<LocalData>(childs);
    }
    return parents;
}
然后你可以创建一个虚拟数据并将其转换并在树中显示:

var sqlData = new List<SqlDataDto>
                  {
                      new SqlDataDto {Id = 1, ParentId = null, Name = "F1"}
                      , new SqlDataDto {Id = 2, ParentId = null, Name = "F2"}
                      , new SqlDataDto {Id = 3, ParentId = 1, Name = "S1"}
                      , new SqlDataDto {Id = 4, ParentId = 2, Name = "S21"}
                      , new SqlDataDto {Id = 5, ParentId = 2, Name = "S22"}
                  };
treeView.ItemsSource = GetHerachy(sqlData);

5-树应该像:

<TreeView Name="treeView">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}" />
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

答案 1 :(得分:1)

您需要使用递归来填充每个对象的Child-List。这是WPF HierarchicalDataTemplate工作所必需的。否则你只能得到第一级。 There是使用Linq方法ForEach()并传递Action Argument的替代方法。以下解决方案非常直接且易于理解:

public List<Product> Products { get; set; }

public MainViewModel()
{
    Products = new List<Product>();

    Products.Add(new Product() { Id = 1, Name = "Main Product 1", ParentId = 0 });
    Products.Add(new Product() { Id = 3, Name = "Sub Product 1", ParentId = 1 });
    Products.Add(new Product() { Id = 4, Name = "Sub Product 2", ParentId = 1 });
    Products.Add(new Product() { Id = 5, Name = "Sub Product 3", ParentId = 1 });
    Products.Add(new Product() { Id = 6, Name = "Sub Product 3.1", ParentId = 5 });


    this.ProcessRootNodes();
}

private void ProcessRootNodes()
{
    var rootNodes = Products.Where(x => x.ParentId == 0).ToList();

    for (int i = 0; i < rootNodes.Count; i++)
    {
rootNodes[i].Children = this.AddChildren(rootNodes[i]);
    }
}

private List<Product> AddChildren(Product entry)
{
    var children = Products.Where(x => x.ParentId == entry.Id).ToList();

    for(int i=0;i<children.Count;i++)
    {
children[i].Children = this.AddChildren(children[i]);
    }

    return children;
}

答案 2 :(得分:0)

// *** HOW TO BUILD HIERARCHY OBJECT WITH UNKNOWN & VARYING LEVELS?

而不是
ObservableCollection<Products> productsHierarchy = new ObservableCollection<Products>();
使用Dictionary<Int64, Products> IdToProduct = new ...

当你循环你的产品;做一个IdToProduct[product.ID] = product;

然后,循环完成的IdToProduct集合并执行;

if(product.ParentID != 0)
{
    IdToProduct[product.ParentID].ChildProducts.Add(product);
}

现在,您的产品 - &gt; ChildProducts关系已映射出来。

(可选)向Products class添加属性:
public bool IsCategory { get { return (ChildProducts.Count >= 1); } } // e.g. Oven
public bool IsProduct { get { return !(IsCategory); } } // e.g. Electric (Oven)

现在,您已经定义了大部分视图模型。

这个article是使用WPF TreeView的事实上的起点。

提示:HierarchicalDataTemplate

的起点
<TreeView.ItemTemplate>
    <HierarchicalDataTemplate DataType="{x:Type local:Products}" 
                              ItemsSource="{Binding ChildProducts}">
      <TextBlock Text="{Binding Name}" />
    </HierarchicalDataTemplate>
</TreeView.ItemTemplate>

您应该创建一个MainViewModel类,其中包含:
public Products RootProduct { get; set; }(通知属性已更改属性)

在你进行SQL解析之后

什么不是;这样做:
RootProduct = IdToProduct.FirstOrDefault(product => (product.Level == 0));

<TreeView ItemsSource="{Binding RootProduct.ChildProducts}">