fir*_*tap 5 linq asp.net-mvc entity-framework
这是我的第一个问题,抱歉我的语言不好.
我有一个像这个模特的桌子;
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
}
Run Code Online (Sandbox Code Playgroud)
这个菜单上有很多行;
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
Run Code Online (Sandbox Code Playgroud)
我有第二堂课准备菜单树;
public class MyMenu:Menu
{
public List<MyMenu> Childs { get;set;}
}
Run Code Online (Sandbox Code Playgroud)
我需要一个linq查询来得到这样的结果;
var result = (...linq..).ToList<MyMenu>();
Run Code Online (Sandbox Code Playgroud)
我正在使用递归函数来获取孩子,但这需要花费太多时间才能获得结果.
如何在一个查询中编写一个句子来获取所有菜单树?
更新:
我想将主菜单存储在表格中.此表将用于用户的访问权限控制.某些行将显示在菜单内,有些行仅用于获取访问权限.
在这种情况下,我需要多次才能获得表树.表树将创建为筛选的用户权限.获取树时,存储在会话中.但很多会话意味着很多RAM.如果有什么快速的方法从我需要的时候从sql获取菜单树,那么我将不会存储在会话中.
如果需要遍历整个树,则应使用存储过程.实体框架特别不适合递归关系.您需要为每个级别发出N + 1个查询,或者急切地加载一组已定义的级别.例如,.Include("Childs.Childs.Childs")将加载三个级别.但是,这将创建一个可怕的查询,您仍然需要为您在开始时未包含的任何其他级别发出N + 1个查询.
在SQL中,你可以使用WITH递归走表,这将是多大的速度比任何实体框架可以做.但是,您的结果将被展平,而不是您将从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]
Run Code Online (Sandbox Code Playgroud)
这看起来要复杂得多.为了确保在菜单中的项目是由父母正确排序,并认为父母的树中的位置,我们需要创建的顺序通过订购的分层表示.我这样做是通过创建一个形式的字符串1.1.1,其中每个项目的顺序基本上附加到父级的层次结构字符串的末尾.我也使用REPLICATE左键填充每个级别的顺序,所以你没有常见的数字字符串排序问题,10之前有类似的东西2,因为它开头1.该@Pad声明只是获取最大长度,我需要垫基于表中的最高顺序号.例如,如果最大订单是类似的123,那么值@Pad将是3,因此订单少于123仍然是三个字符(即001).
一旦你完成了所有这些,SQL的其余部分就非常简单了.您只需选择所有根项目,然后通过遍历树将所有子项联合起来.这并加入每个新的水平.最后,从这个树中选择您需要的信息,按我们创建的层次结构排序字符串排序.
至少对于我的树来说,这个查询是可以接受的快速,但如果复杂性扩展或者需要处理大量的菜单项,可能会比你想要的慢一些.即使使用此查询,对树进行某种缓存也不是一个坏主意.就个人而言,对于像网站导航这样的东西,我建议结合使用儿童动作OutputCache.您可以在布局中调用子操作,导航应该出现在该布局中,它将运行操作以获取菜单,或者从缓存中检索已创建的HTML(如果存在).如果菜单特定于各个用户,则只需确保按自定义更改,并考虑用户ID或自定义字符串中的内容.您也可以只对内存缓存查询本身的结果,但您也可以降低生成HTML的成本.但是,应避免将其存储在会话中.
LINQ to Entities 不支持递归查询。
但是,加载存储在数据库表中的整个树既简单又高效。实体框架的早期版本似乎有一些神话,所以让我们揭开它们的神秘面纱。
您只需要创建一个合适的模型和 FK 关系:
模型:
public class Menu
{
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; }
public ICollection<Menu> Children { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
流畅配置:
modelBuilder.Entity<Menu>()
.HasMany(e => e.Children)
.WithOptional() // EF6
.WithOne() // EF Core
.HasForeignKey(e => e.ParentID);
Run Code Online (Sandbox Code Playgroud)
重要的变化是,为了建立这种关系,ParentID必须是可空的,并且根项目应该使用null而不是0.
现在,有了模型,加载整棵树很简单:
var tree = db.Menu.AsEnumerable().Where(e => e.ParentID == null).ToList();
Run Code Online (Sandbox Code Playgroud)
随着AsEnumerable()我们确保在执行查询时,整个表会在内存中一个简单的非递归检索SELECTSQL。然后我们简单地过滤掉根项目。
就这样。最后,我们有一个包含根节点的列表,其中包含它们的子节点、孙子节点等!
这个怎么运作?不需要/使用懒惰、急切或显式加载。整个魔法由DbContext跟踪和导航属性修复系统提供。
我会尝试这样的事情。
此查询将从数据库中获取所有菜单记录,并创建以 ParentId 为键、以特定父 ID 为值的所有菜单的字典。
// if you're pulling the data from database with EF
var map = (from menu in ctx.Menus.AsNoTracking()
group by menu.ParentId into g
select g).ToDictionary(x => x.Key, x => x.ToList());
Run Code Online (Sandbox Code Playgroud)
现在我们可以很容易地迭代parentIds并创建MyMenu实例
var menusWithChildren = new List<MyMenu>()
foreach(var parentId in map.Keys)
{
var menuWithChildren = new MyMenu { ... }
menuWithChildren.AddRange(map[parentId]);
}
Run Code Online (Sandbox Code Playgroud)
现在你已经有了协会的名单。这样,您将通过引用关联子级和父级(不同嵌套级别之间没有重复引用),但我想知道如果您需要了解根,如何定义它们?我不知道这是否适合你。
| 归档时间: |
|
| 查看次数: |
847 次 |
| 最近记录: |