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 Childs { get; set; }
}

定义 Parent 属性,在表达式中可以这样:

fsql.Select().Where(a => a.Parent.Parent.Parent.Name == "中国").First();

定义 Childs 属性,在表达式中可以这样(子查询):

fsql.Select().Where(a => a.Childs.AsSelect().Any(c => c.Name == "北京")).First();

定义 Childs 属性,还可以使用【级联保存】、【贪婪加载】 等等操作。

fsql.Delete().Where("1=1").ExecuteAffrows();
var repo = fsql.GetRepository();
repo.DbContextOptions.EnableAddOrUpdateNavigateList = true;
repo.DbContextOptions.NoneParameter = true;
repo.Insert(new Area
{
  Code = "100000",
  Name = "中国",
  Childs = new List(new[] {
    new Area
    {
      Code = "110000",
      Name = "北京",
      Childs = new List(new[] {
        new Area{ Code="110100", Name = "北京市" },
        new Area{ Code="110101", Name = "东城区" },
      })
    }
  })
});

1.2 ToTreeList

配置好父子属性之后,就可以这样用了:

var t1 = fsql.Select().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()
  .Where(a => a.Name == "中国")
  .AsTreeCte()
  .ToDelete()
  .ExecuteAffrows(); //删除 中国 下的所有记录

如果软删除:

fsql.Select()
  .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()
  .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()
  .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()
  .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/%E6%9F%A5%E8%AF%A2%E7%88%B6%E5%AD%90%E5%85%B3%E7%B3%BB

2.数据实体类

新建文件Model/TreeDataFsqlFoo.cs

其中GetTreeList()为获取常规List形式, GetTreeItems()是转换为BootstrapBlazor使用的TreeItem格式.

using BootstrapBlazor.Components;
using FreeSql.DataAnnotations;

namespace b05tree;

/// 
/// 无限级分类(父子)是一种比较常用的表设计,表设计中只有 parent_id 字段
/// 
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 Childs { get; set; }



    public static void DemoDatas(IFreeSql fsql)
    {
        var res = fsql!.Select().Count();
        if (res == 0)
        {
            var districts = new TreeDataFsqlFoo
            {
                Code = "001",
                Name = "001_系统管理",
                Childs = new List(new[] {
                    new TreeDataFsqlFoo{
                        Code = "001_01",
                        Name = "001_01_基础数据管理",
                        Childs = new List(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(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(new[] {
                districts,
                    new TreeDataFsqlFoo
                    {
                        Code = "001_03",
                        Name = "001_03_懒加载演示"
                    },
                    new TreeDataFsqlFoo
                    {
                        Code = "001_04",
                        Name = "001_04_懒加载延时演示"
                    }
            });

            var repo = fsql.GetRepository();//仓库类
            repo.DbContextOptions.EnableAddOrUpdateNavigateList = true; //开启一对多,多对多级联保存功能
            repo.Insert(lazyItems);
        }

    }
    public static List GetTreeList(IFreeSql fsql)
    {
        DemoDatas(fsql);
        return fsql.Select().LeftJoin(d => d.ParentCode == d.Parent.Code).ToList();
    }

    public static List GetTreeItems(IFreeSql fsql)
    {
        DemoDatas(fsql);
        var items = fsql.Select().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

Tree 树形控件 + Freesql

通过设置节点 HasChildNode 控制是否显示节点小箭头图片 。通过Tree的 OnExpandNode 委托添加节点

4.后置代码C#

新建文件Pages/TreeFsql.razor.cs

using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;

namespace b05tree.Pages;

/// 
/// 
/// 
public sealed partial class TreeFsql
{
    [Inject] IFreeSql? fsql { get; set; } 


    private List 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(#34;TreeItem: {item.Text} clicked");
        return Task.CompletedTask;
    }

    private Task OnTreeItemChecked(TreeItem item)
    {
        var state = item.Checked ? "选中" : "未选中";
        //TraceChecked.Log(#34;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 items)
    {
        //TraceCheckedItems.Log(#34;当前共选中{items.Count}项");
        return Task.CompletedTask;
    }

}

5.添加Menu链接

添加代码到Shared/NavMenu.razor


    
         TreeFsql
    

6.运行调试

更改和删除后,刷新页面马上可以看到效果.

BootstrapBlazor实战 Tree树形控件使用(2)


项目源码

Blazor100: Blazor入门100天

知识共享许可协议

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow,不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。

AlexChow

今日头条 | 博客园 | 知乎 | Gitee | GitHub

展开阅读全文

页面更新:2024-03-04

标签:递归   东城区   节点   控件   北京   中国   父子   实战   姿势   属性   加载   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top