这是我的第一个问题,对我的语言较弱感到抱歉。

我有一个像这样的 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 中。

09-04 16:25
查看更多