Friday, April 8, 2011

TreeView, Linq-To-SQL recursive data population

I have an IEnumerable(of Employee) with a ParentID/ChildID relationship with itself that I can databind to a TreeView and it populates the hierarchy perfectly. However, I want to be able to manually loop through all the records and create all the nodes programmatically so that I can change the attributes for each node based on the data for that given item/none.

Is there a tutorial out there that explains how to do this? I've seen many that use datasets and datatables but none that show how to do it in Linq to SQL (IEnumerable)

UPDATE:

Here's how I used to do it with a DataSet - I just can't seem to find how to do the same with IEnumerable.

Private Sub GenerateTreeView()
        Dim ds As New DataSet()

        Dim tasktree As New Task(_taskID)

        Dim dt As DataTable = tasktree.GetTaskTree()

        ds.Tables.Add(dt)

        ds.Relations.Add("NodeRelation", dt.Columns("TaskID"), dt.Columns("ParentID"))

        Dim dbRow As DataRow
        For Each dbRow In dt.Rows
            If dbRow("TaskID") = _taskID Then
                Dim node As RadTreeNode = CreateNode(dbRow("Subject").ToString(), False, dbRow("TaskID").ToString())
                RadTree1.Nodes.Add(node)
                RecursivelyPopulate(dbRow, node)
            End If
        Next dbRow
    End Sub 

    Private Sub RecursivelyPopulate(ByVal dbRow As DataRow, ByVal node As RadTreeNode)
        Dim childRow As DataRow
        Dim StrikeThrough As String = ""
        Dim ExpandNode As Boolean = True
        For Each childRow In dbRow.GetChildRows("NodeRelation")
            Select Case childRow("StatusTypeID")
                Case 2
                    StrikeThrough = "ActiveTask"
                Case 3
                    StrikeThrough = "CompletedTask"
                    ExpandNode = False
                Case 4, 5
                    StrikeThrough = "ClosedTask"
                    ExpandNode = False
                Case Else
                    StrikeThrough = "InactiveTask"
                    ExpandNode = False
            End Select
            Dim childNode As RadTreeNode = CreateNode("<span class=""" & StrikeThrough & """><a href=""Task.aspx?taskid=" & childRow("TaskID").ToString() & """>" & childRow("Subject").ToString() & "</a></span>", ExpandNode, childRow("TaskID").ToString())
            node.Nodes.Add(childNode)
            RecursivelyPopulate(childRow, childNode)
            ExpandNode = True
        Next childRow
    End Sub

    Private Function CreateNode(ByVal [text] As String, ByVal expanded As Boolean, ByVal id As String) As RadTreeNode
        Dim node As New RadTreeNode([text])
        node.Expanded = expanded

        Return node
    End Function
From stackoverflow
  • If you just need a way of enumerating the tree you can implement this as a generator, it might look strange, you're probably better of with a user defined enumerator but it's essentially the same thing.

    public interface IGetChildItems<TEntity>
    {
        IEnumerable<TEntity> GetChildItems();
    }
    
    public static IEnumerable<TEntity> Flatten<TEntity>(TEntity root)
        where TEntity : IGetChildItems<TEntity>
    {
        var stack = new Stack<TEntity>();
        stack.Push(root);
        while (stack.Count > 0)
        {
            var item = stack.Pop();
            foreach (var child in item.GetChildItems())
            {
                stack.Push(child);
            }
            yield return item;
        }
    }
    

    The type constraint where TEntity : IGetChildItems is just to signify that you need to abstract how to descend the hierarchy. Without the above code would not compile.

    This will enumerate the tree in a breadth first fashion, it will yield the parent element first then it's children, and then the children of those children. You can easily customize the above code to achieve a different behavior.

    Edit:

    The yield return stuff tells the compiler that it should return a value then continue. yield is a context keyword and it's only allowed inside an iterative statement. A generator is a simple way of writing a IEnumerable data source. The compiler will build a state machine from this code and create an enumerable anonymous class. Apparently the yield keyword does not exist in VB.NET. But you can still write a class which does this.

    Imports System
    Imports System.Collections
    Imports System.Collections.Generic
    
    Public Class HierarchyEnumerator(Of TEntity As IGetChildItems(Of TEntity))
     Implements IEnumerator(Of TEntity), IDisposable, IEnumerator
    
     Public Sub New(ByVal root As TEntity)
      Me.stack = New Stack(Of TEntity)
      Me.stack.Push(root)
     End Sub
    
     Public Sub Dispose()
     End Sub
    
     Public Function MoveNext() As Boolean
      Do While (Me.stack.Count > 0)
       Dim item As TEntity = Me.stack.Pop
       Dim child As TEntity
       For Each child In item.GetChildItems
        Me.stack.Push(child)
       Next
       Me.current = item
       Return True
      Loop
      Return False
     End Function
    
     Public Sub Reset()
      Throw New NotSupportedException
     End Sub
    
     Public ReadOnly Property Current() As TEntity
      Get
       Return Me.current
      End Get
     End Property
    
     Private ReadOnly Property System.Collections.IEnumerator.Current As Object
      Get
       Return Me.Current
      End Get
     End Property
    
     Private current As TEntity
     Private stack As Stack(Of TEntity)
    End Class
    
    EdenMachine : What is "yield"? I don't think VB.NET has an equivalent.
    EdenMachine : I added my previous code because I couldn't make heads or tails of what you were suggesting and how that could apply to what I'm trying to accomplish. I hope that helps clarify my question.

0 comments:

Post a Comment