update

28810
2020-06-28 05:29:41 +08:00
parent bf5bedd158
commit 9523cde12c
3 changed files with 201 additions and 0 deletions

@@ -31,6 +31,7 @@ FreeSql是一个功能强大的 .NET ORM 功能库,支持 .NetFramework 4.0+
* [延时加载](https://github.com/2881099/FreeSql/wiki/%e5%bb%b6%e6%97%b6%e5%8a%a0%e8%bd%bd) * [延时加载](https://github.com/2881099/FreeSql/wiki/%e5%bb%b6%e6%97%b6%e5%8a%a0%e8%bd%bd)
* [贪婪加载!!](https://github.com/2881099/FreeSql/wiki/%e8%b4%aa%e5%a9%aa%e5%8a%a0%e8%bd%bd) * [贪婪加载!!](https://github.com/2881099/FreeSql/wiki/%e8%b4%aa%e5%a9%aa%e5%8a%a0%e8%bd%bd)
* [LinqToSql](https://github.com/2881099/FreeSql/wiki/LinqToSql) * [LinqToSql](https://github.com/2881099/FreeSql/wiki/LinqToSql)
* [查询父子关系]](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2%e7%88%b6%e5%ad%90%e5%85%b3%e7%b3%bb)
* [仓储层Repository](https://github.com/2881099/FreeSql/wiki/Repository) * [仓储层Repository](https://github.com/2881099/FreeSql/wiki/Repository)
* [UnitOfWork](https://github.com/2881099/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83) * [UnitOfWork](https://github.com/2881099/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83)
* [联级保存](https://github.com/2881099/FreeSql/wiki/%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98) * [联级保存](https://github.com/2881099/FreeSql/wiki/%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98)

@@ -19,6 +19,7 @@
* [延时加载](https://github.com/2881099/FreeSql/wiki/%e5%bb%b6%e6%97%b6%e5%8a%a0%e8%bd%bd) * [延时加载](https://github.com/2881099/FreeSql/wiki/%e5%bb%b6%e6%97%b6%e5%8a%a0%e8%bd%bd)
* [贪婪加载!!](https://github.com/2881099/FreeSql/wiki/%e8%b4%aa%e5%a9%aa%e5%8a%a0%e8%bd%bd) * [贪婪加载!!](https://github.com/2881099/FreeSql/wiki/%e8%b4%aa%e5%a9%aa%e5%8a%a0%e8%bd%bd)
* [LinqToSql](https://github.com/2881099/FreeSql/wiki/LinqToSql) * [LinqToSql](https://github.com/2881099/FreeSql/wiki/LinqToSql)
* [查询父子关系]](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2%e7%88%b6%e5%ad%90%e5%85%b3%e7%b3%bb)
* [仓储层Repository](https://github.com/2881099/FreeSql/wiki/Repository) * [仓储层Repository](https://github.com/2881099/FreeSql/wiki/Repository)
* [UnitOfWork](https://github.com/2881099/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83) * [UnitOfWork](https://github.com/2881099/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83)
* [联级保存](https://github.com/2881099/FreeSql/wiki/%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98) * [联级保存](https://github.com/2881099/FreeSql/wiki/%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98)

199
查询父子关系.md Normal file

@@ -0,0 +1,199 @@
无限级分类(父子)是一种比较常用的表设计,每种设计方式突出优势的同时也带来缺陷,如:
- 方法1表设计中只有 parent_id 字段,困扰:查询麻烦(本文可解决);
- 方法2表设计中冗余子级id便于查询困扰添加/更新/删除的时候需要重新计算;
- 方法3表设计中存储左右值编码困扰同上
方法1设计最简单本文解决它的递归查询问题让使用透明化。
## 父子导航属性
FreeSql 支持的导航属性的中,有针对父子关系的设置方式,如下:
```csharp
public class Area
{
[Column(IsPrimary = true)]
public string Code { get; set; }
public string Name { get; set; }
public virtual string ParentCode { get; set; }
[Navigate(nameof(ParentCode))]
public Area Parent { get; set; }
[Navigate(nameof(ParentCode))]
public List<Area> Childs { get; set; }
}
```
定义 Parent 属性,在表达式中可以这样:
```csharp
fsql.Select<Area>().Where(a => a.Parent.Parent.Parent.Name == "中国").First();
```
定义 Childs 属性,在表达式中可以这样(子查询):
```csharp
fsql.Select<Area>().Where(a => a.Childs.AsSelect().Any(c => c.Name == "北京")).First();
```
定义 Childs 属性,还可以使用[【级联保存】](https://github.com/dotnetcore/FreeSql/wiki/%E8%81%94%E7%BA%A7%E4%BF%9D%E5%AD%98)、[【贪婪加载】](https://github.com/2881099/FreeSql/wiki/%e8%b4%aa%e5%a9%aa%e5%8a%a0%e8%bd%bd) 等等操作。
添加测试数据:
```csharp
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、ToTreeList
配置好父子属性之后,就可以这样用了:
```csharp
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 返回。
## 2、AsTreeCte 递归查询
若不做数据冗余的无限级分类表设计递归查询少不了AsTreeCte 正是解决递归查询的封装,方法参数说明:
| 参数 | 描述 |
| -- | -- |
| (可选) pathSelector | 路径内容选择,可以设置查询返回:中国 -> 北京 -> 东城区 |
| (可选) up | false(默认)由父级向子级的递归查询true由子级向父级的递归查询 |
| (可选) pathSeparator | 设置 pathSelector 的连接内容,默认:-> |
| (可选) level | 设置递归层级 |
姿势一AsTreeCte() + ToTreeList
```csharp
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
```csharp
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 参数后,如何返回隐藏字段?
```csharp
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/2881099/FreeSql/wiki/%e5%a4%9a%e8%a1%a8%e6%9f%a5%e8%af%a2)
- [《返回数据》](https://github.com/2881099/FreeSql/wiki/%e8%bf%94%e5%9b%9e%e6%95%b0%e6%8d%ae)
- [《LinqToSql》](https://github.com/2881099/FreeSql/wiki/LinqToSql)
- [《仓储层Repository》](https://github.com/2881099/FreeSql/wiki/Repository)
- [《过滤器、全局过滤器》](https://github.com/2881099/FreeSql/wiki/%e8%bf%87%e6%bb%a4%e5%99%a8)
- [《优化之:延时加载》](https://github.com/2881099/FreeSql/wiki/%e5%bb%b6%e6%97%b6%e5%8a%a0%e8%bd%bd)
- [《优化之:贪婪加载》](https://github.com/2881099/FreeSql/wiki/%e8%b4%aa%e5%a9%aa%e5%8a%a0%e8%bd%bd)
- [《Expression 表达式函数》](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0)
- [《性能》](https://github.com/2881099/FreeSql/wiki/%e6%80%a7%e8%83%bd)
- [《分区、分表、分库》](https://github.com/2881099/FreeSql/wiki/%e5%88%86%e5%8c%ba%e5%88%86%e8%a1%a8)
- [《租户》](https://github.com/2881099/FreeSql/wiki/%e7%a7%9f%e6%88%b7)