十年河东,十年河西,莫欺少年穷。

学无止境,精益求精

难得有清闲的一上午,索性写篇博客。

首先,我们需要准备一张表,如下范例:

create table TreeTable
(
TreeId varchar(100) not null primary key,
NodeName nvarchar(50),--名称
FatherTreeId varchar(100),--父亲Id
)

为了节约时间,我直接沿用项目中的数据,作如下批量插入:

insert into TreeTable(TreeId,NodeName,FatherTreeId)
select [SpClassifyId],[ClassifyName],[ParentClassifyId]
from [dbo].[SM_SupplierClassify]
where [ForefatherClassifyName] in('工程类','精装修类','设计类','营销类')

插入的数据,如下所示:

从上图,我们可以看出,这些数据中有四种大类,分别为:('工程类','精装修类','设计类','营销类'),层级关系通过FatherTreeId构造,当然,此表中的层次不仅仅只有两级,理论上支持N级,类似这种结构的数据现实生活中有很多,如:家谱,人员组织架构,营销模式(微信的吸粉,病毒式营销),甚至传销模式等等

那么,我们如何将上述数据转变为树状结构的数据类型呢?

大家都会想到递归,但如何递归呢?

两种方法,

一、如何构造:

1、递归式查询,从根节点入手,查询多次(效率非常低,在此不作介绍)

2、一次性查询出所有数据,根据FatherTreeId,递归式查询泛型(效率高。只查询一次即可)我们介绍此种方法:

首先,我们构造如下Model

    /// <summary>
/// 我们要构造的树
/// </summary>
public class TreeModel
{
public TreeModel()
{
children = new List<TreeModel>();
}
public string title { get; set; }
public string value { get; set; }
public string key { get; set; }
public List<TreeModel> children { get; set; } public static TreeModel MakeTree(TreeTableModel supplierClassifyModels)
{
TreeModel Tree = new TreeModel();
Tree.title = supplierClassifyModels.NodeName;
Tree.key = supplierClassifyModels.TreeId;
Tree.value = supplierClassifyModels.TreeId;
if (supplierClassifyModels.children != null)
{
foreach (var item in supplierClassifyModels.children)
{
Tree.children.Add(MakeTree(item));
}
}
return Tree;
}
} /// <summary>
/// 数据库表对应的实体Model
/// </summary>
public class TreeTableModel
{
public TreeTableModel()
{
children = new List<TreeTableModel>();
}
public string TreeId { get; set; }
public string NodeName { get; set; }
public string FatherTreeId { get; set; }
//子数据
public List<TreeTableModel> children { get; set; }
}

上述的TreeModel中的 MakeTree 方法用于递归构造树!~_~ 重点

构造方法如下:

        /// <summary>
/// 构造树
/// </summary>
/// <returns></returns>
public static BaseResponse<List<TreeModel>> BuildTree()
{
using (AnFuDBEntities context = new AnFuDBEntities())
{
List<TreeModel> TreeList = new List<TreeModel>();
var data = context.TreeTable.ToList();
var list = new List<TreeTableModel>();
list = Mapper.DynamicMap<List<TreeTableModel>>(data);
List<TreeTableModel> TreeModels = BuildTreeModel(list, string.Empty);
foreach (var item in TreeModels)
{
var Tree = TreeModel.MakeTree(item);
TreeList.Add(Tree);
}
var result = TreeList;
return CommonBaseResponse.SetResponse<List<TreeModel>>(result, true);
}
} /// <summary>
/// 递归方法,构造树状泛型
/// </summary>
/// <param name="AllList"></param>
/// <param name="ParentClassifyId"></param>
/// <returns></returns>
private static List<TreeTableModel> BuildTreeModel(List<TreeTableModel> AllList, string ParentClassifyId)
{
List<TreeTableModel> List = new List<TreeTableModel>(); if (!string.IsNullOrEmpty(ParentClassifyId))
{
List = AllList.Where(A => A.FatherTreeId == ParentClassifyId).OrderBy(A => A.NodeName).ToList();
//
}
else
{
List = AllList.Where(A => string.IsNullOrEmpty(A.FatherTreeId)).ToList(); }
if (List != null)
{
foreach (var item in List)
{
item.children = BuildTreeModel(AllList,item.TreeId);
if (item.children.Count == )
{
item.children = null;
}
} }
return List.ToList();
}

通过上述代码,我们构造了如下数据,工程类(拥有8个子类)->工程类子类中的安装类(拥有11个子类)->......等等,多层次关系!

截止到这儿,树的构造就完成了!

二、如何查询:

如果直接查询数据表,SQL支持的递归查询我们可以用With....As..关键字来进行查询,具体查询语法请参考我的博客:SqlServer共用表达式(CTE)With As 处理递归查询

在此,仅做简单范例,如下:

1、查询子分类,要求带出该子分类的所有上级元素

WITH CTE_Tree(TreeId,NodeName,FatherTreeId )
AS
(SELECT TreeId,NodeName,FatherTreeId
FROM dbo.TreeTable
WHERE NodeName = '电梯工程'
UNION ALL
SELECT o.TreeId,o.NodeName,o.FatherTreeId
FROM dbo.TreeTable o INNER JOIN CTE_Tree oo ON o.TreeId=oo.FatherTreeId)
SELECT distinct * FROM CTE_Tree

查询结果如下:

说明:查询NodeName为电梯工程的分类,带出了电梯工程的所有上级关系!

2、查询上级元素,要求带出所有下级元素

WITH CTE_Tree(TreeId,NodeName,FatherTreeId )
AS
(SELECT TreeId,NodeName,FatherTreeId
FROM dbo.TreeTable
WHERE NodeName = '安装类'
UNION ALL
SELECT o.TreeId,o.NodeName,o.FatherTreeId
FROM dbo.TreeTable o INNER JOIN CTE_Tree oo ON o.FatherTreeId=oo.TreeId)
SELECT distinct * FROM CTE_Tree order by NodeName

查询结果如下:

安装类及11个子类全部查询出来!

如果我们不使用SQL的... with.... as.....语句,要查询出父节点及该节点下的所有子节点,该怎么查询呢?

通过递归List<T>查询方法如下:

 /// <summary>
/// 根据父节点ID 查询该节点及该节点下的所有子节点
/// </summary>
/// <param name="FatherNodeId"></param>
/// <returns></returns>
public static List<TreeTableModel> QueryTreeByFatherId(string FatherNodeId)
{
using (AnFuDBEntities context = new AnFuDBEntities())
{
List<TreeModel> TreeList = new List<TreeModel>();
var data = context.TreeTable.ToList();
var list = new List<TreeTableModel>();
list = Mapper.DynamicMap<List<TreeTableModel>>(data);
List<TreeTableModel> treeNodes = new List<TreeTableModel>();
//安装类 对应的Id : 650FEF5E-21E6-404A-B8F7-12E82548D432
new QueryTreeModel().GetTreeNodes(list, FatherNodeId, ref treeNodes);
var ef = list.Where(A => A.TreeId == FatherNodeId).FirstOrDefault();
treeNodes.Add(ef);//为了和数据库 as with 递归查询一致,将此节点加入返回值中
return treeNodes;
}
} /// <summary>
/// 根据父类节点,查询出所有子节点
/// </summary>
/// <param name="list">数据源,非树状结构</param>
/// <param name="TreeId">父节点Id</param>
/// <param name="treeNodes">得到的子节点</param>
public void GetTreeNodes(List<TreeTableModel> list, string TreeId, ref List<TreeTableModel> treeNodes)
{
if (list == null)
return;
List<TreeTableModel> sublist;
if (!string.IsNullOrWhiteSpace(TreeId))
{
sublist = list.Where(t => t.FatherTreeId == TreeId).ToList();
}
else
{
sublist = list.Where(t => string.IsNullOrWhiteSpace(t.FatherTreeId)).ToList();
}
if (!sublist.Any())
return;
foreach (var item in sublist)
{
treeNodes.Add(new TreeTableModel() { TreeId=item.TreeId, FatherTreeId=item.FatherTreeId, NodeName=item.NodeName});
GetTreeNodes(list, item.TreeId, ref treeNodes);
}
}

执行上述待代码:

 //安装类 对应的Id : 650FEF5E-21E6-404A-B8F7-12E82548D432
AnfuUser.QueryTreeByFatherId("650FEF5E-21E6-404A-B8F7-12E82548D432");

结果如下:

根据截图可知,我们查询到12条记录!

那么,我们执行SQL如下,看看结果是否一致,如下:

根据上下两个截图,我们得知,查询结果一致!

那么,如果我们要根据子节点,查询该节点及所有上级节点,怎么通过程序查询呢?

代码如下:

  /// <summary>
/// 根据子ID 获取自身节点及所有父节点
/// </summary>
/// <param name="FatherNodeId"></param>
/// <returns></returns>
public static List<TreeTableModel> QueryTreeByChildId(string FatherNodeId)
{
using (AnFuDBEntities context = new AnFuDBEntities())
{
List<TreeModel> TreeList = new List<TreeModel>();
var data = context.TreeTable.ToList();
var list = new List<TreeTableModel>();
list = Mapper.DynamicMap<List<TreeTableModel>>(data);
List<TreeTableModel> treeNodes = new List<TreeTableModel>();
//安装类 对应的Id : 650FEF5E-21E6-404A-B8F7-12E82548D432
new QueryTreeModel().GetTreeNodesByChildId(list, FatherNodeId, ref treeNodes);
var ef = list.Where(A => A.TreeId == FatherNodeId).FirstOrDefault();
treeNodes.Add(ef);//为了和数据库 as with 递归查询一致,将此节点加入返回值中
return treeNodes;
}
} /// <summary>
/// 根据子节点,查询出所有父节点
/// </summary>
/// <param name="list">数据源,非树状结构</param>
/// <param name="TreeId">子节点Id</param>
/// <param name="treeNodes">得到的子节点</param>
public void GetTreeNodesByChildId(List<TreeTableModel> list, string TreeId, ref List<TreeTableModel> treeNodes)
{
if (list == null)
return;
var ef = list.Where(A => A.TreeId == TreeId).FirstOrDefault();
if (ef == null)
return;
List<TreeTableModel> sublist;
if (!string.IsNullOrWhiteSpace(TreeId))
{
var FatherTreeId = ef.FatherTreeId;
sublist = list.Where(t => t.TreeId == FatherTreeId).ToList();
}
else
{
sublist = list.Where(t => !string.IsNullOrWhiteSpace(t.FatherTreeId)).ToList();
}
if (!sublist.Any())
return;
foreach (var item in sublist)
{
treeNodes.Add(new TreeTableModel() { TreeId = item.TreeId, FatherTreeId = item.FatherTreeId, NodeName = item.NodeName });
GetTreeNodesByChildId(list, item.TreeId, ref treeNodes);
}
}

执行上述代码,我们查询电梯工程及它的所有上级节点,

AnfuUser.QueryTreeByChildId("75269B3A-0086-40A9-BE18-62820E662B69");

结果如下:

我们再通过SQL查询如下:

WITH CTE_Tree(TreeId,NodeName,FatherTreeId )
AS
(SELECT TreeId,NodeName,FatherTreeId
FROM dbo.TreeTable
WHERE TreeId = '75269B3A-0086-40A9-BE18-62820E662B69'
UNION ALL
SELECT o.TreeId,o.NodeName,o.FatherTreeId
FROM dbo.TreeTable o INNER JOIN CTE_Tree oo ON o.TreeId=oo.FatherTreeId)
SELECT distinct * FROM CTE_Tree

结果如下:

比对二者查询结果是否一致呢?

@陈卧龙的博客

C# 递归构造树状数据结构(泛型),如何构造?如何查询?的更多相关文章

  1. 菜鸟笔记:node.js+mysql中将JSON数据构建为树(递归制作树状菜单数据接口)

    初学Web端开发,今天是第一次将所学做随笔记录,肯定存在多处欠妥,望大家海涵:若有不足,望大家批评指正. 进实验室后分配到的第一个项目,需要制作一个不确定层级树形菜单的数据接口,对于从来没实战编过程的 ...

  2. javascript中用闭包递归遍历树状数组

    做公司项目时,要求写一个方法,方法的参数为一个菜单数组集合和一个菜单id,菜单数组的格式为树状json,如下面所示: [{"id":28,"text":&quo ...

  3. Apple Tree POJ - 3321 dfs序列构造树状数组(好题)

    There is an apple tree outside of kaka's house. Every autumn, a lot of apples will grow in the tree. ...

  4. hdu 3887 Counting Offspring(DFS序【非递归】+树状数组)

    题意: N个点形成一棵树.给出根结点P还有树结构的信息. 输出每个点的F[i].F[i]:以i为根的所有子结点中编号比i小的数的个数. 0<n<=10^5 思路: 方法一:直接DFS,进入 ...

  5. POJ 2155 Matrix 【二维树状数组】(二维单点查询经典题)

    <题目链接> 题目大意: 给出一个初始值全为0的矩阵,对其进行两个操作. 1.给出一个子矩阵的左上角和右上角坐标,这两个坐标所代表的矩阵内0变成1,1变成0. 2.查询某个坐标的点的值. ...

  6. 牛客网 暑期ACM多校训练营(第二场)J.farm-STL(vector)+二维树状数组区间更新、单点查询 or 大暴力?

    开心.jpg J.farm 先解释一下题意,题意就是一个n*m的矩形区域,每个点代表一个植物,然后不同的植物对应不同的适合的肥料k,如果植物被撒上不适合的肥料就会死掉.然后题目将每个点适合的肥料种类( ...

  7. 洛谷 P3368 【模板】树状数组 2(区间修改点查询)

    题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数数加上x 2.求出某一个数的值 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. ...

  8. 设计模式-Composite(结构型模式) 用于 递归构建 树 状 的组合结构,与Decorator的区别是 Composite旨在通过构造子类而添加新操作,而Decorator直接添加新操作。

    以下代码来源: 设计模式精解-GoF 23种设计模式解析附C++实现源码 //Component.h #pragma once class Component { public: Component( ...

  9. 如何构造树状 JSON 数据 JSON-Tree

    十年河东,十年河西,莫欺骚年穷...打错一个字哈.~_~ 接着上一篇博客,上一篇博客是=使用数据库结合LINQ构造的,为了方便理解,本篇采用泛型分组进行构造. 有兴趣的小虎斑可以参考上一篇博客:如何构 ...

随机推荐

  1. VS2015安装水晶报表

    最近在做一个打印功能,本来是不想用水晶报表的.想直接用微软原生的报表rdlc完成,但是整了一个上午老是打印乱码,且网上找资料找也找不出. 无奈放弃,然后就想到用水晶报表了,以前用过水晶报表,不过一直都 ...

  2. 微信公众号通过图片选取接口上传到阿里oss

    前言 之前写过一篇微信JS-SDK的使用方法,可进行参考 https://www.cnblogs.com/fozero/p/10256862.html 配置并调用公众号接口权限 1.配置权限微信公众号 ...

  3. msf中exploit的web_delivery模块

    背景:目标设备存在远程文件包含漏洞或者命令注入漏洞,想在目标设备上加载webshell,但不想在目标设备硬盘上留下任何webshell文件信息 解决思路:让目标设备从远端服务器加载webshell代码 ...

  4. Linux之数据库操作

    一.mysql基本操作 ,连接数据库 mysql -u root -p -h 127.0.0.1 mysql -u root -p -h 192.168.12.56 ,授予远程连接的权限 grant ...

  5. [JavaScript] audio在浏览器中自动播放

    audio 在浏览器中自动播放 autoplay 属性 autoplay 属性规定一旦音频就绪马上开始播放. 如果设置了该属性,音频将自动播放. 使用 autoplay 属性进行播放 //使用auto ...

  6. .Net语言 APP开发平台——Smobiler学习日志:如何在手机中调用邮件发送接口

    最前面的话:Smobiler是一个在VS环境中使用.Net语言来开发APP的开发平台,也许比Xamarin更方便 一.目标样式 我们要实现上图中的效果,需要如下的操作: 二.发送邮件代码 VB: Pr ...

  7. JS 事件绑定,监听,委托(代理)

    我们经常会遇到JavaScript的事件机制,例如,事件绑定.事件监听.事件委托(事件代理)等.这些名词是什么意思呢,有什么作用呢? 在JavaScript中,有三种常用的绑定事件的方法: 在DOM元 ...

  8. 驰骋工作流引擎JFlow与activiti的对比之4种高级分支同步模式

    多重选择(Multiple Choice) 在流程中,当一个活动完成后,有多个分支进行选择,可以选择执行其中的一个或者N个分支. 例子:比如去世博园玩,在门口检票后,可以选择A-E个片区中的N个进行观 ...

  9. 从.Net到Java学习第四篇——spring boot+redis

    从.Net到Java学习系列目录 “学习java已经十天,有时也怀念当初.net的经典,让这语言将你我相连,怀念你......”接上一篇,本篇使用到的框架redis.FastJSON. 环境准备 安装 ...

  10. C#的自动拼接Sql语句Insert方法及思路

    思路: 1.想想插入语句,大概是这样的一个框架:INSERT INTO 表名 (数据库列名) values (值) 2.这里要3个变量是不固定的,分别是:表名.数据库列名.值: a.表名我们这里很容易 ...