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

前言

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

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

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

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

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

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

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

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

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

c# 复制代码
/// <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 类

c# 复制代码
/// <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. 获取初级菜单

    c# 复制代码
    /// <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. 获取二级菜单

    c# 复制代码
    /// <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. 获取三级(最终)菜单

    c# 复制代码
    /// <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 拼接写查询方法

    c# 复制代码
    /// <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. 业务层递归处理并返回集合数据给前端

    c# 复制代码
    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. 总结

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

往期精彩

  1. 分享一个 .NET 通过监听器拦截 EF 消息写日志的详细例子
  2. 不会使用 EF Core 的 Code First 模式?来看看这篇文章,手把手地教你
  3. EF Core 性能很差?试试这 6 个小技巧
  4. 如何在 EF Core 中使用乐观并发控制
  5. EF Core 在实际开发中,如何分层?

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

相关推荐
△曉風殘月〆5 小时前
WPF MVVM入门系列教程(二、依赖属性)
c#·wpf·mvvm
逐·風7 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
m0_6569747410 小时前
C#中的集合类及其使用
开发语言·c#
九鼎科技-Leo10 小时前
了解 .NET 运行时与 .NET 框架:基础概念与相互关系
windows·c#·.net
九鼎科技-Leo12 小时前
什么是 ASP.NET Core?与 ASP.NET MVC 有什么区别?
windows·后端·c#·asp.net·mvc·.net
.net开发12 小时前
WPF怎么通过RestSharp向后端发请求
前端·c#·.net·wpf
小乖兽技术12 小时前
C#与C++交互开发系列(二十):跨进程通信之共享内存(Shared Memory)
c++·c#·交互·ipc
幼儿园园霸柒柒13 小时前
第七章: 7.3求一个3*3的整型矩阵对角线元素之和
c语言·c++·算法·矩阵·c#·1024程序员节
平凡シンプル15 小时前
C# EF 使用
c#
丁德双15 小时前
winform 加载 office excel 插入QRCode图片如何设定位置
c#·excel