TreeView, Linq-To-SQL рекурсивное заполнение данных - PullRequest
3 голосов
/ 26 февраля 2009

У меня есть IEnumerable (из Employee) с отношением ParentID / ChildID, который я могу связать с TreeView, и он идеально заполняет иерархию. Однако я хочу иметь возможность вручную циклически проходить по всем записям и создавать все узлы программным способом, чтобы я мог изменять атрибуты для каждого узла на основе данных для данного заданного элемента / ни одного.

Есть ли учебник, который объясняет, как это сделать? Я видел много таких, которые используют наборы данных и таблицы данных, но ни один из них не показывает, как это сделать в Linq to SQL (IEnumerable)

UPDATE:

Вот как я делал это с DataSet - я просто не могу найти, как сделать то же самое с 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

1 Ответ

3 голосов
/ 26 февраля 2009

Если вам просто нужен способ перечисления дерева, вы можете реализовать его как генератор, это может выглядеть странно, вам, вероятно, лучше использовать пользовательский перечислитель, но по сути это одно и то же.

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;
    }
}

Ограничение типа, в котором TEntity: IGetChildItems просто указывает на то, что вам необходимо абстрагироваться от спуска иерархии. Без приведенного выше кода не будет компилироваться.

Это будет сначала перечислять дерево по ширине, сначала будет давать родительский элемент, потом его потомки, а затем потомки этих потомков. Вы можете легко настроить приведенный выше код для достижения другого поведения.

Edit:

Материал возвращаемого результата сообщает компилятору, что он должен вернуть значение, а затем продолжить. yield является ключевым словом контекста, и он разрешен только внутри итеративного оператора. Генератор - это простой способ написания источника данных IEnumerable. Компилятор создаст конечный автомат из этого кода и создаст перечисляемый анонимный класс. Очевидно, ключевое слово yield не существует в VB.NET. Но вы все равно можете написать класс, который делает это.

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
...