分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法

简介: 分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法

前言

树形结构是一种很常见的数据结构,类似于现实生活中的树的结构,具有根节点、父子关系和层级结构。

所谓根节点,就是整个树的起始节点。

节点则是树中的元素,每个节点可以有零个或多个子节点,节点按照层级排列,根节点属于第一层,其子节点属于第二层,以此类推,没有子节点的节点,则称为叶子,是最后一层。

父子关系就是节点之间的关系,每个节点都有父节点。

树形结构的应用非常广泛,例如在数据库中用来表示组织结构、目录结构,还用于实现树状菜单、文件系统等。

树形结构的灵活性和层次性使其成为处理具有层级关系数据的有力工具。

常见的树形结构包括二叉树、平衡树、B树等,它们在各个领域都有不同的应用场景和算法实现。

下面分享 EF6 查询并返回树形结构数据的 2 个思路和具体实现方法。

1. EF 生成数据表的实体类

/// <summary>
/// HTFP14 表实体类
/// </summary>
public partial class HTFP14
{
  public string COMPHT14 { get; set; }
  public string ACDEHT14 { get; set; }
  public string PGRPHT14 { get; set; }
  public string PKEYHT14 { get; set; }
  public string DESCHT14 { get; set; }
  public Nullable<decimal> PVALHT14 { get; set; }
  public string PSTRHT14 { get; set; }
  public string GLNOHT14 { get; set; }
  public string PCDEHT14 { get; set; }
  public string ATLVHT14 { get; set; }
  public string USERHT14 { get; set; }
  public System.DateTime LMDTMHT14 { get; set; }
}

2. 创建用于前端的 ViewModel 类

/// <summary>
/// 主菜单 UI 树形结构 ViewModel 类
/// </summary>
public class MainMenuViewModel
{
  [Description("菜单层次")]
  public int MenuLevel { get; set; }
  [Description("菜单码")]
  public string MenuCode { get; set; }
  [Description("菜单名称")]
  public string MenuName { get; set; }
  [Description("菜单对外显示名称")]
  public string MenuLabel
  {
    get
    {
      return $"{MenuCode} - {MenuName}";
    }
  }
  [Description("父菜单码")]
  public string ParentMenuCode { get; set; }
  [Description("菜单URL")]
  public string MenuUrl { get; set; }
  [Description("菜单授权用户")]
  public string MenuUser { get; set; }
  [Description("是否禁止")]
  public bool Disabled
  {
    get
    {
      if (string.IsNullOrEmpty(MenuUser))
      {
        return true;
      }
      return false;
    }
  }
  [Description("菜单排序")]
  public decimal MenuOrder { get; set; }
  [Description("子级菜单")]
  public IList<MainMenuViewModel> Children { get; set; }
}

3. 数据准备

  1. 获取初级菜单
/// <summary>
/// 查询第一级菜单
/// </summary>
/// <returns></returns>
private IQueryable<MainMenuViewModel> GetFirstMenu()
{
  var query = from t1 in _dbContext.HTFP14 
        where t1.PGRPHT14 == "MNGP" 
        select new MainMenuViewModel
        {
          MenuCode = t1.PKEYHT14,
          MenuName = t1.DESCHT14,
          ParentMenuCode = "",
          MenuUrl = "",
          MenuUser = ""
        };
  return query;
}

2.获取二级菜单

/// <summary>
/// 查询第二级菜单
/// </summary>
/// <param name="companyCode"></param>
/// <returns></returns>
private IQueryable<MainMenuViewModel> GetSecondMenu(string companyCode)
{
  var query = from t1 in _dbContext.HTFP14
        where t1.PGRPHT14 == "MUGP" && 
            t1.COMPHT14 == companyCode
        select new MainMenuViewModel
        {
          MenuCode = t1.PKEYHT14,
          MenuName = t1.DESCHT14,
          ParentMenuCode = t1.PCDEHT14,
          MenuUrl = "",
          MenuUser = ""
        };
  return query;
}

3.获取三级(最终)菜单

/// <summary>
/// 查询第三级(最终)菜单
/// </summary>
/// <param name="companyCode"></param>
/// <returns></returns>
private IQueryable<MainMenuViewModel> GetThirdMenu(string menuUser, string companyCode)
{
  var query = from t1 in _dbContext.HTFP02
        where t1.COMPHT02 == companyCode && 
            t1.STSHT02 == "A"
        join t2 in (from t21 in _dbContext.HTFP03 where t21.USRMNHT03==menuUser && t21.COMPHT03==companyCode select t21) on t1.MNUCDHT02 equals t2.MNUCDHT03 into t1_t2
        from t12 in t1_t2.DefaultIfEmpty()
        select new MainMenuViewModel
        {
          MenuCode = t1.MNUCDHT02,
          MenuName = t1.DESCHT02,
          ParentMenuCode = t1.MNUGPHT02,
          MenuUrl = t1.URLHT02,
          MenuUser = t12.USRMNHT03
        };
  return query;
}

4.解释:这样分开查询,简化代码,避免太复杂的 Linq 拼接

方法一

思路:使用 Linq 语法拼接查询,具体步骤如下:

  1. 在数据层用 Linq 拼接写查询方法
/// <summary>
/// 查询主菜单树形结构数据
/// </summary>
/// <param name="companyCode"></param>
/// <returns></returns>
public IQueryable<object> QueryMainMenus(string menuUser, string companyCode)
{
  var query1 = GetFirstMenu();
  var query2 = GetSecondMenu(companyCode);
  var query3 = GetThirdMenu(menuUser, companyCode);
  var query = from t1 in query1
        select new
        {
          MenuCode = t1.MenuCode,
          MenuName = t1.MenuName,
          ParentMenuCode = t1.ParentMenuCode,
          MenuUrl = t1.MenuUrl,
          MenuUser = t1.MenuUser,
          Children = (from t2 in query2
                where t2.ParentMenuCode == t1.MenuCode
                select new
                {
                  MenuCode = t2.MenuCode,
                  MenuName = t2.MenuName,
                  ParentMenuCode = t2.ParentMenuCode,
                  MenuUrl = t2.MenuUrl,
                  MenuUser = t2.MenuUser,
                  Children = (from t3 in query3
                        where t3.ParentMenuCode == t2.MenuCode
                        select new
                        {
                          MenuCode = t3.MenuCode,
                          MenuName = t3.MenuName,
                          ParentMenuCode = t3.ParentMenuCode,
                          MenuUrl = t3.MenuUrl,
                          MenuUser = t3.MenuUser
                        })
                })
        };
  return query;
}

2.在业务层直接调用方法生成 List 返回给前端

3.总结
逻辑比较简单,有多个菜单可以一直添加下去,但代码会变得很长,所以比较适合事先知道层级并且层级数量不多的场景。

方法二(推荐)

思路:实体类 + 递归方法,具体步骤如下:

  1. 数据层 EF 使用 Union 方法返回整个树形结构数据:
/// <summary>
/// 查询主菜单树形结构数据
/// </summary>
/// <param name="companyCode"></param>
/// <returns></returns>
public IQueryable<MainMenuViewModel> QueryMainMenus(string menuUser, string companyCode)
{
  var query1 = GetFirstMenu();
  var query2 = GetSecondMenu(companyCode);
  var query3 = GetThirdMenu(menuUser, companyCode);
  var query = query1.Union(query2).Union(query3);
  return query;
}

2.业务层递归处理并返回集合数据给前端

public List<MainMenuViewModel> QueryMainMenus(string menuUser, string companyCode)
{
  var list = hTFP02Reposition.QueryMainMenus(menuUser, companyCode).ToList();
  var list2 = GetData(list);
  return list2;
}
/// <summary>
/// 处理树形结构数据
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
private List<MainMenuViewModel> GetData(List<MainMenuViewModel> source)
{
  List<MainMenuViewModel> nodes = source.Where(x => x.ParentMenuCode == "").Select(x => x).ToList();
  foreach (MainMenuViewModel item in nodes)
  {
    item.Children = GetChildren(source, item);
  }
  return nodes;
}
/// <summary>
/// 递归处理树形结构数据
/// </summary>
/// <param name="source"></param>
/// <param name="node"></param>
/// <returns></returns>
private IList<MainMenuViewModel> GetChildren(List<MainMenuViewModel> source, MainMenuViewModel node)
{
  IList<MainMenuViewModel> childrens = source.Where(c => c.ParentMenuCode == node.MenuCode).Select(x => x).ToList();
  foreach (MainMenuViewModel item in childrens)
  {
    item.Children = GetChildren(source, item);
  }
  return childrens;
}

3.总结
代码比较简单,但逻辑相对不如第一种方法好理解,递归方法的性能略逊于第一种方法,但可扩展性比较强,适用于无法事先知道层级数量的树形数据结构。

往期精彩

我是老杨,一个奋斗在一线的资深研发老鸟,让我们一起聊聊技术,聊聊程序人生,共同学习,共同进步


相关文章
|
2月前
|
SQL 缓存 开发框架
分享一个 .NET EF6 应用二级缓存提高性能的方法
分享一个 .NET EF6 应用二级缓存提高性能的方法
|
2月前
|
程序员 数据库
分享 2 个 .NET EF 6 只更新某些字段的方法
分享 2 个 .NET EF 6 只更新某些字段的方法
|
2月前
|
数据库
分享一个 .NET EF 6 扩展 Where 的方法
分享一个 .NET EF 6 扩展 Where 的方法
|
1月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
28 7
|
29天前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
39 0
|
2月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
37 0
|
2月前
|
开发框架 前端开发 安全
ASP.NET MVC 如何使用 Form Authentication?
ASP.NET MVC 如何使用 Form Authentication?
|
2月前
|
开发框架 .NET
Asp.Net Core 使用X.PagedList.Mvc.Core分页 & 搜索
Asp.Net Core 使用X.PagedList.Mvc.Core分页 & 搜索
94 0
|
5月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
160 0
|
5月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
69 0