.net 检索所有子项及其子项,递归SQL
考虑数据库中的以下行:.net 检索所有子项及其子项,递归SQL,.net,sql,linq,entity-framework,linq-to-entities,.net,Sql,Linq,Entity Framework,Linq To Entities,考虑数据库中的以下行: Id | Parent __________________ 1 null 2 1 3 2 4 3 5 null 6 5 每个Id具有nullParent的都是“所有者”/“超级父级” 从绩效角度来看,收集父母及其子女的最佳方法是什么?我应该使用LINQ还是存储过程 我希望最终结果是IEnumerable在SQL中,您可以使用CTE进行查
Id | Parent
__________________
1 null
2 1
3 2
4 3
5 null
6 5
每个Id
具有null
Parent
的都是“所有者”/“超级父级”
从绩效角度来看,收集父母及其子女的最佳方法是什么?我应该使用LINQ还是存储过程
我希望最终结果是
IEnumerable
在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行的子行。如果表不太大,您最好通过这样做返回整个表 db.类别 一旦将整个categories表提取到Entity Framework中,EF将使用关系跨度来修复objectgraph,这样当您执行category.SubCategories时,您将获得所有子类。 这样做的好处是,sql不会很复杂,因为它基本上是从类别中选择*的。EF将在修复对象图方面做大量的艰苦工作,以便所有的子对象都与父对象正确对齐 您还可以使用其他人提到的关于使用公共表表达式的内容 我在书中提到了两个这样的概念 5-11使用关系跨度
5-2加载完整的对象图(CTE)您也可以使用纯SQL解决方案;下面是SQL Server的一个示例。为不同的数据库管理器重新编写它并不困难:
/* 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,则表示该节点是循环的一部分,因此它没有根节点。数据库实际上并不用于执行任意深度递归。下面是本地完成的所需操作
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);
}
}
List items=context.items.ToList();
Dictionary itemsbyd=items.ToDictionary(item=>item.Id);
Dictionary itemsByRoot=新字典();
列表周期=新列表();
foreach(项目中的项目)
{
HashSet seenIt=新HashSet();
项目父项=项目;
while(parent.ParentId!=null&&!seenIt[parent.Id])
{
参见nit.Add(父项Id);
parent=itemsbyd[parent.ParentId];
}
if(parent.ParentId==null)
{
如果(!itemsByRoot.ContainsKey(parent.Id))
{
itemsByRoot[parent.Id]=新列表();
}
itemsByRoot[parent.Id]。添加(项);
}
其他的
{
周期。添加(项目);
}
}
您的意思是要将其自身作为父项的项目?您是指订单吗?如果您想要一个包含所有子项的IEnumerable,您可以从父项不为null的表中选择*,所以您的问题必须更多…第2行的父项是第2行?哎哟。@Eric,不,从来都不是这样,要么它是null
,要么它指向另一个“行”。@Andomar,错过了,谢谢:)@Kendrick,正如您所看到的,它是一个带有IEnumerable
的IEnumerable
,其中每个IEnumerable
表示父级以下的子级。这样做而不是公认的答案会对性能有何好处?这感觉比实际情况要复杂得多。