Обновлено 23 марта 2016
Кравченко Виктор

LINQ-рекурсия, или как получить список потомков дерева неограниченной глубины

VB.NET MVC ASP.NET LINQ Visual Studio 2013 Web Entity Framework
01

Бывает в некоторых таблицах используется иерархия в виде дерева с бесконечным уровнем вложенности. Проиллюстрируем сказанное на примере модели:

02
Структура таблицы span class=monoNodes/span. Дизайнер таблиц MS SQL 2008 R2
Структура таблицы Nodes. Дизайнер таблиц MS SQL 2008 R2
03
Таблица span class=monoNodes/span на диаграмме MS SQL 2008 R2
Таблица Nodes на диаграмме MS SQL 2008 R2
04
Сущность span class=monoNode/span в EF-дизайнере VS 2013
Сущность Node в EF-дизайнере VS 2013
05

Модель заполнена данными:

06
1
2
3
4
5
6
7
8
9
10
11
12
13
ID ParentID Name 1 NULL Корневая папка 1 2 1 Папка 1-1 3 1 Папка 1-2 4 1 Папка 1-3 5 2 Папка 1-2-1 6 2 Папка 1-2-2 7 2 Папка 1-2-3 8 NULL Корневая папка 2 9 8 Папка 2-1 10 9 Папка 2-1-1 11 8 Папка 2-2 12 9 Папка 2-1-2
07 Задача:
Получить всех потомков, например, Корневой папки 1 при помощи LINQ-запроса
08

Прямого обхода всех потомков в LINQ не существует (за исключением, конечно же, SelectMany для первого уровня вложенности), поэтому для решения поставленной задачи необходимо написать 2 LINQ-расширения:

09 VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<System.Runtime.CompilerServices.Extension> _
Public Function Flatten(Of T)(source As IEnumerable(Of T), childSelector As Func(Of T, IEnumerable(Of T))) As IEnumerable(Of T) Return source _ .Flatten(Function(item, anyobject) childSelector(item)) End Function
<System.Runtime.CompilerServices.Extension> _
Public Function Flatten(Of T)(source As IEnumerable(Of T), childSelector As Func(Of T, IEnumerable(Of T), IEnumerable(Of T))) As IEnumerable(Of T) Return source _ .Concat(source.Where(Function(item) childSelector(item, source) IsNot Nothing) _ .SelectMany(Function(item) childSelector(item, source) _ .Flatten(childSelector) _ ) _ ) End Function
10

Использование:

11 VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Dim nodes = db.Nodes _ .Where(Function(n) n.ID = 1) _ ' Выбираем родителя .Flatten(Function(n) n.Children) _ ' Задаем свойство-селектор потомков .ToList() ' Получаем список всех потомков, включая родителя Dim nodes = db.Nodes _ .Where(Function(n) n.ID = 1) _ .Flatten(Function(n) n.Children) _ .Select(Function(n) n.ID) _ ' Выбираем только идетификаторы .ToList() ' Получаем список идентификаторов (List(Of Integer)) всех потомков, включая родителя
' Результат: ' 1 Корневая папка 1 ' 2 Папка 1-1 ' 3 Папка 1-2 ' 4 Папка 1-3 ' 5 Папка 1-2-1 ' 6 Папка 1-2-2 ' 7 Папка 1-2-3
13

Похожие запросы:

  • Flattening hierarchical data with Linq in VB.NET
  • Flatten List in LINQ
  • Flatten LINQ collection object with nested object collections
  • How to flatten tree via LINQ
  • Flattening Collection Hierarchies with LINQ
  • Рекурсивная выборка с помощью LINQ
  • LINQ и рекурсия\дерево
  • Flatten Nested Loops with LINQ and SelectMany()
  • Рекурсивный обход на LINQ
  • Select one parent property and all children using linq
comments powered by HyperComments