TreeView, рекурсивное заполнение данных Linq-To-SQL
-
09-09-2019 - |
Вопрос
У меня есть IEnumerable (of Employee) с отношением ParentID / childID к самому себе, которое я могу привязать к TreeView, и оно идеально заполняет иерархию.Однако я хочу иметь возможность вручную перебирать все записи и создавать все узлы программно, чтобы я мог изменять атрибуты для каждого узла на основе данных для данного элемента / нет.
Есть ли какой-нибудь учебник, который объясняет, как это сделать?Я видел много таких, которые используют наборы данных и таблицы данных, но ни один из них не показывает, как это сделать в Linq to SQL (IEnumerable)
Обновить:
Вот как я раньше делал это с набором данных - я просто не могу найти, как сделать то же самое с 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
Решение
Если вам просто нужен способ перечисления дерева, вы можете реализовать это как генератор, это может показаться странным, вам, вероятно, лучше использовать определяемый пользователем перечислитель, но это, по сути, одно и то же.
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 - это просто для обозначения того, что вам нужно абстрагироваться от того, как спускаться по иерархии.Без приведенного выше кода он бы не компилировался.
Это позволит сначала перечислить дерево в ширину, сначала оно выдаст родительский элемент, затем его дочерние элементы, а затем дочерние элементы этих дочерних элементов.Вы можете легко настроить приведенный выше код для достижения другого поведения.
Редактировать:
Материал yield return сообщает компилятору, что он должен вернуть значение, а затем продолжить.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