考虑数据库中的以下行:
Id | Parent
__________________
1 null
2 1
3 2
4 3
5 null
6 5
Id
null
的每个Parent
都是“所有者”/“超级家长”。
收集父母及其子女的最佳方法,表现明智?我应该使用 LINQ 还是存储过程?
我希望最终结果为IEnumerable<IEnumerable<int>>
。
答案 0 :(得分:10)
在SQL中,您可以使用CTE进行查询。例如,要检索其父级和树中最高父级的节点列表:
declare @t table (id int, parent int)
insert @t (id, parent) values (1, null), (2,1), (3,2), (4,3), (5,null), (6,5)
; with cte as (
select id, parent, id as head
from @t
where parent is null
union all
select child.id, child.parent, parent.head
from @t child
join cte parent
on parent.id = child.parent
)
select *
from cte
这给出了:
id parent head
1 NULL 1
2 1 1
3 2 1
4 3 1
5 NULL 5
6 5 5
请注意,我更改了您的示例数据,因此第2行不再是其自身的子项,而是第1行的子项。
答案 1 :(得分:2)
您也可以使用纯SQL解决方案;这是SQL Server的示例。为不同的DB管理器重新编写它并不困难:
/* Create table */
CREATE TABLE dbo.Nodes (ID int NOT NULL PRIMARY KEY, Parent int)
/* Insert sample data */
INSERT INTO Nodes VALUES (1,NULL)
INSERT INTO Nodes VALUES (2,1)
INSERT INTO Nodes VALUES (3,2)
INSERT INTO Nodes VALUES (4,3)
INSERT INTO Nodes VALUES (5,NULL)
INSERT INTO Nodes VALUES (6,5)
/* Create recursive function */
CREATE function dbo.fn_Root(@ID int) returns int
AS BEGIN
DECLARE @R int
SELECT @R = CASE WHEN Parent IS NULL THEN ID
ELSE dbo.fn_Root(Parent)
END
FROM Nodes
WHERE id = @id
RETURN @R
END
/* Query the table */
SELECT ID, Parent, dbo.fn_Root(ID) AS Root
FROM Nodes
/* Also, in SQL Server you can create a calculated column */
ALTER TABLE Nodes ADD Root AS dbo.fn_Root(id)
这是基本版本。但是如果你的数据有闭环(不是树形结构),那么这个会失败。为了防止代码进入无限循环,可以像这样改进函数:
CREATE function dbo.fn_Root(@ID int, @Initial int) returns int
AS BEGIN
DECLARE @R int
DECLARE @Parent int
SELECT @Parent = Parent FROM Nodes WHERE ID = @ID
IF @Parent IS NULL
SELECT @R = @ID /* No parent, the root is the node itself */
ELSE
IF @Parent = @Initial
/* We have returned to initial node: endless loop. We return NULL to indicate no root exists */
SELECT @R = NULL
ELSE
/* The root will be the root of the parent node */
SELECT @R = dbo.fn_Root(@Parent,@Initial)
RETURN @R
END
/* Query the table */
SELECT ID, Parent, dbo.fn_Root(ID,ID) AS Root FROM Nodes
通过此修改,如果函数返回NULL,则表示该节点是循环的一部分,因此它没有根节点。
答案 2 :(得分:1)
如果表格不是太大,那么最好的办法是通过这样做来返回整个表格 db.Categories
将整个类别表提取到实体框架后,EF将使用关系跨度来修复对象图,因此当您执行category.SubCategories时,您将获得所有子项。 这样做的好处是,你的sql不会很复杂,因为它基本上是从类别中选择*。 EF在修复对象图时将完成大部分艰苦工作,以便所有孩子都能与父母正确对齐。
您还可以使用其他人提到的有关使用公用表表达式的内容。
我在书中介绍了两个这样的概念。
5-11使用关系跨度 5-2加载完整对象图(CTE)
答案 3 :(得分:-3)
数据库实际上并不意味着进行任意深度递归。这是在本地完成的所需操作。
List<Item> items = context.Items.ToList();
Dictionary<int, Item> itemsById = items.ToDictionary(item => item.Id);
Dictionary<int, List<Item>> itemsByRoot = new Dictionary<int, List<Item>>();
List<Item> cyclicals = new List<Item>();
foreach(Item item in items)
{
HashSet<int> seenIt = new HashSet<int>();
Item parent = item;
while (parent.ParentId != null && !seenIt[parent.Id])
{
seenIt.Add(parent.Id);
parent = itemsById[parent.ParentId];
}
if (parent.ParentId == null)
{
if (!itemsByRoot.ContainsKey(parent.Id))
{
itemsByRoot[parent.Id] = new List<Item>();
}
itemsByRoot[parent.Id].Add(item);
}
else
{
cyclicals.Add(item);
}
}