这是我的第一个问题,对我的语言较弱感到抱歉。
我有一个像这样的 table 。
public class Menu
{
[Key]
public int ID {get;set;}
public int ParentID {get;set;}
public string MenuName {get;set;}
public int OrderNo {get;set;}
public bool isDisplayInMenu {get;set;} // Menu or just for Access Authority
}
菜单上有很多这样的行;
ID ParentID MenuName Order
--- --------- ------------- ------
1 0 Main.1 1 >> if ParentID==0 is Root
2 1 Sub.1.1 1
3 2 Sub.1.2 2
4 0 Main.2 2
5 4 Sub.2.1 1
6 4 Sub.2.2 2
我有第二节课准备菜单树。
public class MyMenu:Menu
{
public List<MyMenu> Childs { get;set;}
}
我需要一个linq查询来得到这样的结果;
var result = (...linq..).ToList<MyMenu>();
我正在使用递归函数来获取子项,但这需要太多时间才能获得结果。
如何编写一个查询中的所有菜单树的句子?
更新:
我想将主菜单存储在表格中。并且此表将对用户使用访问权限控制。某些行将显示在菜单内,有些行将仅用于获取访问权限。
在这种情况下,我需要很多次才能获得表树。该表树将被创建为过滤后的用户权限。当获取树时,存储在 session 中。但是许多 session 意味着大量的RAM。如果有什么需要时从sql获取菜单树的快速方法,那么我将不存储在 session 中。
最佳答案
如果您需要遍历整棵树,则应使用存储过程。 Entity Framework 特别不适用于递归关系。您要么需要为每个级别发出N + 1个查询,要么急于加载一组定义的级别。例如,.Include("Childs.Childs.Childs")
会加载三个级别。但是,这将创建一个可怕的查询,并且您仍然需要针对开始时不包含的任何其他级别发出N + 1个查询。
在SQL中,您可以使用WITH
递归遍历表,这将比Entity Framework所能做的任何事情都要快得多。但是,您的结果将变平,而不是从Entity Framework返回的对象图。例如:
DECLARE @Pad INT = (
SELECT MAX([Length])
FROM (
SELECT LEN([Order]) AS [Length] FROM [dbo].[Menus]
) x
);
WITH Tree ([Id], [ParentId], [Name], [Hierarchy]) AS
(
SELECT
[ID],
[ParentID],
[MenuName],
REPLICATE('0', @Pad - LEN([Order])) + CAST([Order] AS NVARCHAR(MAX))
FROM [dbo].[Menus]
WHERE [ParentID] = 0 -- root
UNION ALL
SELECT
Children.[ID],
Children.[ParentID],
Children.[MenuName],
Parent.[Hierarchy] + '.' + REPLICATE('0', @Pad - LEN(Children.[Order])) + CAST(Children.[Order] AS NVARCHAR(MAX)) AS [Hierarchy]
FROM [dbo].[Menus] Children
INNER JOIN Tree AS Parent
ON Parent.[ID] = Children.[ParentID]
)
SELECT
[ID],
[ParentID],
[MenuName]
FROM Tree
ORDER BY [Hierarchy]
这看起来比实际要复杂得多。为了确保菜单项在父项中正确排序,并确保它们在该父项树中的位置,我们需要为要排序的项创建分层表示。我在这里通过创建形式为
1.1.1
的字符串来执行此操作,其中基本上每个项目的顺序都附加到父级层次结构字符串的末尾。我还使用REPLICATE
将每个级别的顺序左移,因此您不会遇到数字的字符串顺序常见的问题,在10
之前,像2
这样的东西就出现了,因为它以1
开头。 @Pad
声明仅根据表中的最高订单号获取我需要填充的最大长度。例如,如果最大订单类似于123
,则@Pad
的值将为3,因此小于123
的订单仍为三个字符(即001
)。一旦克服了所有这些,SQL的其余部分将变得非常简单。您只需选择所有根项目,然后通过遍历树与所有 child 合并即可。这并加入每个新级别。最后,从该树中选择所需的信息,这些信息由我们创建的层次结构排序字符串排序。
至少对于我的树而言,此查询的速度可以接受,但是如果复杂度增加或有大量菜单项需要处理,它可能会比您希望的慢一些。即使使用此查询,对树进行某种形式的缓存也不是一个坏主意。就个人而言,对于网站导航之类的东西,我建议结合使用子 Action 和
OutputCache
。您在应该在其中显示导航的布局中调用子操作,它将运行该操作以获取菜单,或者从缓存中检索已创建的HTML(如果存在)。如果菜单是特定于单个用户的,则只需确保您因自定义而有所不同,并考虑用户的ID或自定义字符串中的某些内容。您也可以只对查询结果本身进行内存缓存,但同时也可以减少生成HTML的成本。但是,应避免将其存储在 session 中。