TreeView, Linq-To-SQL 재귀 데이터 채우기
-
09-09-2019 - |
문제
TreeView에 데이터 바인딩할 수 있고 계층 구조를 완벽하게 채울 수 있는 ParentID/ChildID 관계가 있는 IEnumerable(직원)이 있습니다.그러나 모든 레코드를 수동으로 반복하고 모든 노드를 프로그래밍 방식으로 생성하여 해당 항목에 대한 데이터를 기반으로 각 노드의 속성을 변경할 수 있기를 원합니다.
이 작업을 수행하는 방법을 설명하는 튜토리얼이 있나요?데이터 세트와 데이터 테이블을 사용하는 많은 것을 보았지만 Linq에서 SQL(IEnumerable)로 수행하는 방법을 보여주는 것은 없습니다.
업데이트:
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
해결책
트리를 열거하는 방법이 필요한 경우 이를 생성기로 구현할 수 있습니다. 이상해 보일 수 있으며 사용자 정의 열거자를 사용하는 것이 더 나을 수도 있지만 본질적으로 동일합니다.
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 데이터 소스를 작성하는 간단한 방법입니다.컴파일러는 이 코드에서 상태 시스템을 구축하고 열거 가능한 익명 클래스를 생성합니다.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