BootstrapBlazor实战 Tree树形控件使用(2)
继续上篇实战BootstrapBlazor树型控件Tree内容, 本篇主要讲解整合Freesql orm快速制作数据库后台维护页面
demo演示的是Sqlite驱动,FreeSql支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/神通/人大金仓/翰高/华为GaussDB/MsAccess
1.Freesql处理树形分类
无限级分类(父子)是一种比较常用的表设计,我们直接设计表中包含 parent_id 字段,然后递归查询,fsql内置了方法让使用透明化。
1.1 父子导航属性
FreeSql 导航属性之中,有针对父子关系的设置方式,如下:
public class Area
{
[Column(IsPrimary = true)]
public string Code { get; set; }
public string Name { get; set; }
public string ParentCode { get; set; }
[Navigate(nameof(ParentCode))]
public Area Parent { get; set; }
[Navigate(nameof(ParentCode))]
public List<Area> Childs { get; set; }
}
定义 Parent 属性,在表达式中可以这样:
fsql.Select<Area>().Where(a => a.Parent.Parent.Parent.Name == "中国").First();
定义 Childs 属性,在表达式中可以这样(子查询):
fsql.Select<Area>().Where(a => a.Childs.AsSelect().Any(c => c.Name == "北京")).First();
定义 Childs 属性,还可以使用【级联保存】、【贪婪加载】 等等操作。
fsql.Delete<Area>().Where("1=1").ExecuteAffrows();
var repo = fsql.GetRepository<Area>();
repo.DbContextOptions.EnableAddOrUpdateNavigateList = true;
repo.DbContextOptions.NoneParameter = true;
repo.Insert(new Area
{
Code = "100000",
Name = "中国",
Childs = new List<Area>(new[] {
new Area
{
Code = "110000",
Name = "北京",
Childs = new List<Area>(new[] {
new Area{ Code="110100", Name = "北京市" },
new Area{ Code="110101", Name = "东城区" },
})
}
})
});
1.2 ToTreeList
配置好父子属性之后,就可以这样用了:
var t1 = fsql.Select<Area>().ToTreeList();
Assert.Single(t1);
Assert.Equal("100000", t1[0].Code);
Assert.Single(t1[0].Childs);
Assert.Equal("110000", t1[0].Childs[0].Code);
Assert.Equal(2, t1[0].Childs[0].Childs.Count);
Assert.Equal("110100", t1[0].Childs[0].Childs[0].Code);
Assert.Equal("110101", t1[0].Childs[0].Childs[1].Code);
查询数据本来是平面的,ToTreeList 方法将返回的平面数据在内存中加工为树型 List 返回。
1.3 AsTreeCte 递归删除
很常见的无限级分类表功能,删除树节点时,把子节点也处理一下。
fsql.Select<Area>()
.Where(a => a.Name == "中国")
.AsTreeCte()
.ToDelete()
.ExecuteAffrows(); //删除 中国 下的所有记录
如果软删除:
fsql.Select<Area>()
.Where(a => a.Name == "中国")
.AsTreeCte()
.ToUpdate()
.Set(a => a.IsDeleted, true)
.ExecuteAffrows(); //软删除 中国 下的所有记录
1.4 AsTreeCte 递归查询
若不做数据冗余的无限级分类表设计,递归查询少不了,AsTreeCte 正是解决递归查询的封装,方法参数说明:
| 参数 | 描述 |
|---|---|
| (可选) pathSelector | 路径内容选择,可以设置查询返回:中国 -> 北京 -> 东城区 |
| (可选) up | false(默认):由父级向子级的递归查询,true:由子级向父级的递归查询 |
| (可选) pathSeparator | 设置 pathSelector 的连接符,默认:-> |
| (可选) level | 设置递归层级 |
通过测试的数据库:MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、Firebird、达梦、人大金仓、南大通用、翰高
姿势一:AsTreeCte() + ToTreeList
var t2 = fsql.Select<Area>()
.Where(a => a.Name == "中国")
.AsTreeCte() //查询 中国 下的所有记录
.OrderBy(a => a.Code)
.ToTreeList(); //非必须,也可以使用 ToList(见姿势二)
Assert.Single(t2);
Assert.Equal("100000", t2[0].Code);
Assert.Single(t2[0].Childs);
Assert.Equal("110000", t2[0].Childs[0].Code);
Assert.Equal(2, t2[0].Childs[0].Childs.Count);
Assert.Equal("110100", t2[0].Childs[0].Childs[0].Code);
Assert.Equal("110101", t2[0].Childs[0].Childs[1].Code);
// WITH "as_tree_cte"
// as
// (
// SELECT 0 as cte_level, a."Code", a."Name", a."ParentCode"
// FROM "Area" a
// WHERE (a."Name" = '中国')
// union all
// SELECT wct1.cte_level + 1 as cte_level, wct2."Code", wct2."Name", wct2."ParentCode"
// FROM "as_tree_cte" wct1
// INNER JOIN "Area" wct2 ON wct2."ParentCode" = wct1."Code"
// )
// SELECT a."Code", a."Name", a."ParentCode"
// FROM "as_tree_cte" a
// ORDER BY a."Code"
姿势二:AsTreeCte() + ToList
var t3 = fsql.Select<Area>()
.Where(a => a.Name == "中国")
.AsTreeCte()
.OrderBy(a => a.Code)
.ToList();
Assert.Equal(4, t3.Count);
Assert.Equal("100000", t3[0].Code);
Assert.Equal("110000", t3[1].Code);
Assert.Equal("110100", t3[2].Code);
Assert.Equal("110101", t3[3].Code);
//执行的 SQL 与姿势一相同
姿势三:AsTreeCte(pathSelector) + ToList
设置 pathSelector 参数后,如何返回隐藏字段?
var t4 = fsql.Select<Area>()
.Where(a => a.Name == "中国")
.AsTreeCte(a => a.Name + "[" + a.Code + "]")
.OrderBy(a => a.Code)
.ToList(a => new {
item = a,
level = Convert.ToInt32("a.cte_level"),
path = "a.cte_path"
});
Assert.Equal(4, t4.Count);
Assert.Equal("100000", t4[0].item.Code);
Assert.Equal("110000", t4[1].item.Code);
Assert.Equal("110100", t4[2].item.Code);
Assert.Equal("110101", t4[3].item.Code);
Assert.Equal("中国[100000]", t4[0].path);
Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path);
Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path);
Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path);
// WITH "as_tree_cte"
// as
// (
// SELECT 0 as cte_level, a."Name" || '[' || a."Code" || ']' as cte_path, a."Code", a."Name", a."ParentCode"
// FROM "Area" a
// WHERE (a."Name" = '中国')
// union all
// SELECT wct1.cte_level + 1 as cte_level, wct1.cte_path || ' -> ' || wct2."Name" || '[' || wct2."Code" || ']' as cte_path, wct2."Code", wct2."Name", wct2."ParentCode"
// FROM "as_tree_cte" wct1
// INNER JOIN "Area" wct2 ON wct2."ParentCode" = wct1."Code"
// )
// SELECT a."Code" as1, a."Name" as2, a."ParentCode" as5, a.cte_level as6, a.cte_path as7
// FROM "as_tree_cte" a
// ORDER BY a."Code"
更多姿势...请根据代码注释进行尝试 https://github.com/dotnetcore/FreeSql/wiki/查询父子关系
2.数据实体类
新建文件Model/TreeDataFsqlFoo.cs
其中GetTreeList()为获取常规List形式, GetTreeItems()是转换为BootstrapBlazor使用的TreeItem格式.
using BootstrapBlazor.Components;
using FreeSql.DataAnnotations;
namespace b05tree;
/// <summary>
/// 无限级分类(父子)是一种比较常用的表设计,表设计中只有 parent_id 字段
/// </summary>
public class TreeDataFsqlFoo
{
//https://github.com/dotnetcore/FreeSql/wiki/%E6%9F%A5%E8%AF%A2%E7%88%B6%E5%AD%90%E5%85%B3%E7%B3%BB
[Column(IsPrimary = true, StringLength = 6)]
public string Code { get; set; }
[Column(StringLength = 20, IsNullable = false)]
public string Name { get; set; }
[Column(StringLength = 6)]
public string ParentCode { get; set; }
[Navigate(nameof(ParentCode))]
public TreeDataFsqlFoo Parent { get; set; }
[Navigate(nameof(ParentCode))]
public List<TreeDataFsqlFoo> Childs { get; set; }
public static void DemoDatas(IFreeSql fsql)
{
var res = fsql!.Select<TreeDataFsqlFoo>().Count();
if (res == 0)
{
var districts = new TreeDataFsqlFoo
{
Code = "001",
Name = "001_系统管理",
Childs = new List<TreeDataFsqlFoo>(new[] {
new TreeDataFsqlFoo{
Code = "001_01",
Name = "001_01_基础数据管理",
Childs = new List<TreeDataFsqlFoo>(new[] {
new TreeDataFsqlFoo{
Code = "001_01_01",
Name = "001_01_01_教师"
},
new TreeDataFsqlFoo{
Code = "001_01_02",
Name = "001_01_02_职工"
}
})
},
new TreeDataFsqlFoo{
Code = "001_02",
Name = "001_02_餐厅数据管理",
Childs = new List<TreeDataFsqlFoo>(new[] {
new TreeDataFsqlFoo{
Code = "001_02_01",
Name = "001_02_01_厨师"
},
new TreeDataFsqlFoo{
Code = "001_02_02",
Name = "001_02_02_服务员"
}
})
}
})
};
var lazyItems = new List<TreeDataFsqlFoo>(new[] {
districts,
new TreeDataFsqlFoo
{
Code = "001_03",
Name = "001_03_懒加载演示"
},
new TreeDataFsqlFoo
{
Code = "001_04",
Name = "001_04_懒加载延时演示"
}
});
var repo = fsql.GetRepository<TreeDataFsqlFoo>();//仓库类
repo.DbContextOptions.EnableAddOrUpdateNavigateList = true; //开启一对多,多对多级联保存功能
repo.Insert(lazyItems);
}
}
public static List<TreeDataFsqlFoo> GetTreeList(IFreeSql fsql)
{
DemoDatas(fsql);
return fsql.Select<TreeDataFsqlFoo>().LeftJoin(d => d.ParentCode == d.Parent.Code).ToList();
}
public static List<TreeItem> GetTreeItems(IFreeSql fsql)
{
DemoDatas(fsql);
var items = fsql.Select<TreeDataFsqlFoo>().LeftJoin(d => d.ParentCode == d.Parent.Code)
.ToList(a => new TreeItem()
{
Text = a.Name,
Id = a.Code,
ParentId = a.ParentCode
});
// 算法获取属性结构数据
return items.CascadingTree().ToList();
}
}
3.Razor页面
新建文件Pages/TreeFsql.razor
上半部分是Tree控件,下半部分是BootstrapBlazor的Table组件,用于维护后台数据
@page "/treefsql"
@using b05tree.Data
<h3>Tree 树形控件 + Freesql</h3>
<p>
通过设置节点 <code>HasChildNode</code> 控制是否显示节点小箭头图片 。通过Tree的 <code>OnExpandNode</code> 委托添加节点
</p>
<Tree ClickToggleNode="true" Items="@GetLazyItems()" ShowCheckbox="true" OnTreeItemChecked="@OnTreeItemChecked" OnExpandNode="OnExpandNode" />
<Table TItem="TreeDataFsqlFoo"
IsPagination="true"
IsStriped="true"
IsBordered="true"
AutoGenerateColumns="true"
ShowSearch="true"
ShowToolbar="true"
ShowExtendButtons="true"
DoubleClickToEdit=true
ShowColumnList=true
ShowCardView=true>
</Table>
4.后置代码C#
新建文件Pages/TreeFsql.razor.cs
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
namespace b05tree.Pages;
/// <summary>
///
/// </summary>
public sealed partial class TreeFsql
{
[Inject] IFreeSql? fsql { get; set; }
private List<TreeItem> GetLazyItems()
{
var ret = TreeDataFsqlFoo.GetTreeItems(fsql);
ret[1].Text += "_懒加载";
ret[1].HasChildNode = true;
ret[2].Text += "_懒加载延时";
ret[2].HasChildNode = true;
ret[2].Key = "Delay";
return ret;
}
private Task OnTreeItemClick(TreeItem item)
{
//Trace.Log($"TreeItem: {item.Text} clicked");
return Task.CompletedTask;
}
private Task OnTreeItemChecked(TreeItem item)
{
var state = item.Checked ? "选中" : "未选中";
//TraceChecked.Log($"TreeItem: {item.Text} {state}");
return Task.CompletedTask;
}
private static async Task OnExpandNode(TreeItem item)
{
if (!item.Items.Any() && item.HasChildNode && !item.ShowLoading)
{
item.ShowLoading = true;
if (item.Key?.ToString() == "Delay")
{
await Task.Delay(800);
}
item.Items.AddRange(new TreeItem[]
{
new TreeItem()
{
Text = "懒加载子节点1",
HasChildNode = true
},
new TreeItem()
{
Text = "懒加载延时子节点2",
HasChildNode = true,
Key = "Delay"
},
new TreeItem() { Text = "懒加载子节点3" }
});
item.ShowLoading = false;
}
}
private Task OnTreeItemChecked(List<TreeItem> items)
{
//TraceCheckedItems.Log($"当前共选中{items.Count}项");
return Task.CompletedTask;
}
}
5.添加Menu链接
添加代码到Shared/NavMenu.razor
<div class="nav-item px-3">
<NavLink class="nav-link" href="treefsql">
<span class="oi oi-plus" aria-hidden="true"></span> TreeFsql
</NavLink>
</div>
6.运行调试
更改和删除后,刷新页面马上可以看到效果.

项目源码
关联项目
FreeSql QQ群:4336577(已满)、8578575(已满)、52508226(在线)
BA & Blazor QQ群:795206915、675147445
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow(包含链接: https://github.com/densen2014 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。
AlexChow
今日头条 | 博客园 | 知乎 | Gitee | GitHub
BootstrapBlazor实战 Tree树形控件使用(2)的更多相关文章
- BootstrapBlazor实战-Tree树形控件使用(1)
实战BootstrapBlazor树型控件Tree的使用, 以及整合Freesql orm快速制作数据库后台维护页面 demo演示的是Sqlite驱动,FreeSql支持多种数据库,MySql/Sql ...
- vue+element-ui之tree树形控件有关子节点和父节点之间的各种选中关系详解
做后端管理系统,永远是最蛋疼.最复杂也最欠揍的事情,也永远是前端开发人员最苦逼.最无奈也最尿性的时刻.蛋疼的是需求变幻无穷,如同二师兄的三十六般变化:复杂的是开发难度寸步难行,如同蜀道难,难于上青天: ...
- vue_elementUI_ tree树形控件 获取选中的父节点ID
el-tree 的 this.$refs.tree.getCheckedKeys() 只可以获取选中的id 无法获取选中的父节点ID想要获取选中父节点的id;需要如下操作1. 找到工程下的node_m ...
- Element ui tree树形控件获取当前节点id和父节点id
低版本Element ui tree树形控件获取当前节点id和父节点id的方法:点击查看 最新版本Element ui tree树形控件获取当前节点id和父节点id教程: 1.找到node_modul ...
- ElementUI Tree树形控件renderContent return时报错
问题描述: 使用Tree树形控件使用render-content渲染时return后报错或npm run dev时候报错,报错信息相同,如下: 问题分析: renderContent函数中需要使用js ...
- vue-element Tree树形控件通过id默认选中
一.设置 1.给树形控件设置 ref="tree" node-key="id" 2.在获取数据的位置加上 this.$nextTick(() => { t ...
- Element ui 使用 Tree 树形控件
使用树形控件需要映入 jsx才能运行链接:https://github.com/vuejs/babel-plugin-transform-vue-jsx#usage npm install\ babe ...
- easyui Tree树形控件的异步加载
Tree控件 $('#partyOrgTree').tree({ checkbox: false, url: getDataUrl, onClick: function (node) { getDiv ...
- elementUI Tree 树形控件--官方文档
一.基础用法基础的树形结构展示,props相当于一个对实体类对像 <template> <el-tree :data="data" :props="de ...
随机推荐
- thinkphp5 composer安装phpexcel插件及使用
1: 首先composer加载phpexcel插件 composer require phpoffice/phpexcel 2: 页面引入 use PHPExcel_IOFactory; use PH ...
- 一比一还原axios源码(四)—— Axios类
axios源码的分析,到目前为止,算上第0章已经四章了,但是实际上,还都没有进入axios真正的主线,我们来简单回顾下.最开始我们构建了get请求,写了重要的buildURL方法,然后我们处理请求体请 ...
- MySQL8.0降级安装5.7
本文旨在自我学习使用,如有任何疑问请及时联系博主 前言 基于OpenHarmony的FA数字管家服务端 默认情况下,Ubuntu20.04安装MySQL的版本为8.0.但8.0更加严格的加密规则,使得 ...
- 分享一些阅读Java相关框架源码的经验
昨天和部分网友进行了线上的交流,交流中有不少网友提到阅读源码比较难,不知道怎么上手.胖哥分享了一些个人经验,这里总结一下. 阅读源码实际上是Debug源码 其实所谓的阅读源码并不是单纯的阅读,而是调试 ...
- NaviCat连接提示驱动程序IM004错误
今天一打开NaviCat提示驱动程序IM004错误,百度了好几种方法都不起作用,多次尝试后总结出一套方法: 一.到控制面板--卸载程序--卸载NaviCat Clien 注意可能有多个版本的,2008 ...
- 详解数据库三种删除方法: delete drop truncate
Delete Delete :删除数据表中的行(可以删除某一行,也可以在不删除数据表的情况下删除所有行). 删除某一行:Delete from 数据表名称 where 列名称=值: 删除所有行:Del ...
- [实验吧](web)因缺思厅的绕过 源码审计绕过
0x00 直接看源码吧 早上写了个注入fuzz的脚本,无聊回到实验吧的题目进行测试,发现了这道题 地址:http://ctf5.shiyanbar.com/web/pcat/index.php 分析如 ...
- 16经典的SPI Flash的扇区擦除flash_se功能
一设计功能 对SPI_flash进行扇区擦除,分为写指令和扇区擦除两个时序部分. 二设计知识点 我简单理解flash,第一它是掉电不丢失数据的存储器,第二它每次写入新数据前首先得擦除数据,分为扇区擦除 ...
- S7-1200在博途V16中新建数据块(DB)
硬件环境: S7-1200 CPU V4.4(6ES7 212-1AE40-0XB0) 软件环境: (1)Windows 10 Professional SP1 64位 (2)STEP7 V16 SP ...
- GC 是什么?为什么要有 GC?
GC 是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误 的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动 监测对象是否超过作用域从而达到自动回收内存 ...