Моделирование рекурсии CTE в С#
Скажите, что у меня есть следующий CTE, который возвращает уровень некоторых данных дерева (модель смежности), которые у меня есть (взяты из Иерархические данные в Linq - параметры и производительность)
WITH hierarchy_cte(id, parent_id, data, lvl) AS
(
SELECT id, parent_id, data, 0 AS lvl
FROM dbo.hierarchical_table
WHERE (parent_id IS NULL)
UNION ALL
SELECT t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
FROM dbo.hierarchical_table AS t1
INNER JOIN hierarchy_cte AS h ON t1.parent_id = h.id
)
SELECT id, parent_id, data, lvl
FROM hierarchy_cte AS result
Мне было интересно, будет ли увеличение производительности за счет рекурсии в С# вместо SQL. Может ли кто-нибудь показать мне, как выполнять ту же работу, что и CTE, с рекурсивной функцией С#, предполагая, что у меня есть IQueryable, где Tree является сущностью, представляющей запись в иерархической таблице? Что-то вроде:
public void RecurseTree(IQueryable<Tree> tree, Guid userId, Guid parentId, int level)
{
...
currentNode.level = x
...
Recurse(tree... ,level + 1)
}
Было бы здорово видеть, что это легко сделать, используя выражение лямбда.
Ответы
Ответ 1
Рекурсия в SQL Server ужасно медленна, но она работает.
Я должен сказать, что T-SQL несколько ограничен, но он никогда не должен был делать все эти операции в первую очередь. Я не думаю, что вы можете сделать это с помощью IQueryable, если вы хотите запустить этот экземпляр экземпляра SQL Server, но вы можете сделать это в памяти на компьютере, на котором выполняется код с использованием LINQ-to-Objects, в относительно компактный способ.
Вот один из способов сделать это:
class TreeNode
{
public int Id;
public int? ParentId;
}
static void Main(string[] args)
{
var list = new List<TreeNode>{
new TreeNode{ Id = 1 },
new TreeNode{ Id = 4, ParentId = 1 },
new TreeNode{ Id = 5, ParentId = 1 },
new TreeNode{ Id = 6, ParentId = 1 },
new TreeNode{ Id = 2 },
new TreeNode{ Id = 7, ParentId= 2 },
new TreeNode{ Id = 8, ParentId= 7 },
new TreeNode{ Id = 3 },
};
foreach (var item in Level(list, null, 0))
{
Console.WriteLine("Id={0}, Level={1}", item.Key, item.Value);
}
}
private static IEnumerable<KeyValuePair<int,int>> Level(List<TreeNode> list, int? parentId, int lvl)
{
return list
.Where(x => x.ParentId == parentId)
.SelectMany(x =>
new[] { new KeyValuePair<int, int>(x.Id, lvl) }.Concat(Level(list, x.Id, lvl + 1))
);
}
Ответ 2
Истинно рекурсивные lambdas (и по умозаключениям Expression
s) технически возможны, но в значительной степени безумный. Я также ожидал бы, что любой синтаксический анализатор (L2S, EF и т.д.), За исключением, возможно, LINQ-to-Objects, просто сходит с ума, пытаясь разблокировать это.
Короче: вам следовало бы рассмотреть этот неподдерживаемый механизм для Expression
.
Наконец, обратите внимание, что только потому, что вы пишете Expression
, это не значит, что вы выполняете его на С# - на самом деле, скорее всего, наоборот: если вы активно пишете Expression
(а не делегат или процедурный код) Я предполагаю, что он будет работать с парсером (если вы не использовали .AsQueryable()
для перевода его в LINQ-to-Objects).