前言
以前我们存储层次结构常用 Id
+ ParentId
` 的方式,例如:
Id |
ParentId |
Name |
1 |
null |
总公司 |
2 |
1 |
分公司 1 |
3 |
1 |
分公司 2 |
4 |
2 |
部门 A |
5 |
4 |
小组 X |
6 |
4 |
小组 Y |
这种方式查询效率不高,比如查询分公司 1 下的所有小组,必须使用递归。
针对这个问题,如果你是使用 Sql Server
,可以尝试一下 HierarchyId
。
HierarchyId
HierarchyId
是一种长度可变的 Sql Server
数据类型,它能存储带有层次结构的数据。
HierarchyId
数据类型的值可以直接表示树层次结构中的位置,例如:
Id |
Name |
/ |
总公司 |
/1/ |
分公司 1 |
/2/ |
分公司 2 |
/1/1/ |
部门 A |
/1/1/1/ |
小组 X |
/1/1/2/ |
小组 Y |
HierarchyId
可以使用下列函数:
GetAncestor
:取得第 n 个祖先
GetDescendant
:取得第 n 个子节点
GetLevel
:取得级别
GetRoot
:取得根
Parse
:将字符串转换为 HierarchyId
ToString
:将 HierarchyId
转换为字符串,与 parse
正好相反
比如,查询分公司 1 下的所有小组,可以使用下列语句:
1
|
select * from t where [Id].GetLevel() = 3 AND [Id].GetAncestor(2) = '/1/'
|
HierarchyId 数据类型详情请参看官方文档:https://docs.microsoft.com/zh-cn/sql/relational-databases/hierarchical-data-sql-server?view=sql-server-ver15
代码示例
下面,我们通过一个示例,演示如何使用 Entity Framework Core
操作 HierarchyId
数据类型。
建表
执行下列 Sql,在数据库中建表:
1
2
3
4
|
create table Organizations(
Id hierarchyid primary key,
Name nvarchar(50)
);
|
创建项目
创建控制台应用程序,然后引用 nuget 包 EntityFrameworkCore.SqlServer.HierarchyId
。
定义数据模型
新建 Organization.cs
,代码如下:
1
2
3
4
5
|
public class Organization
{
public HierarchyId Id { get; set; }
public string Name { get; set; }
}
|
【注意】 Id
的类型是 HierarchyId
。
新建 DemoContext.cs
,代码如下:
1
2
3
4
5
6
7
8
9
10
|
public class DemoContext : DbContext
{
public DbSet<Organization> Organizations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connectionString = "...";
optionsBuilder.UseSqlServer(connectionString, config => config.UseHierarchyId());
}
}
|
使用 config.UseHierarchyId()
开启 HierarchyId
映射。
增删改查
现在,我们可以对 HierarchyId
数据类型进行操作了。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
//增
using (var db = new DemoContext())
{
db.Organizations.AddRange(
new Organization { Id= HierarchyId.Parse("/"), Name= "总公司" }
,new Organization { Id = HierarchyId.Parse("/1/"), Name = "分公司1" }
,new Organization { Id = HierarchyId.Parse("/2/"), Name = "分公司2" }
, new Organization { Id = HierarchyId.Parse("/1/1/"), Name = "部门A" }
, new Organization { Id = HierarchyId.Parse("/1/1/1/"), Name = "小组X" }
, new Organization { Id = HierarchyId.Parse("/1/1/2/"), Name = "小组Y" }
);
db.SaveChanges();
}
//删除分公司2
using (var db = new DemoContext())
{
db.Organizations.Remove(db.Organizations.Where(p => p.Id == HierarchyId.Parse("/2/")).First());
db.SaveChanges();
}
//修改小组名称
using (var db = new DemoContext())
{
var team = db.Organizations.Where(p => p.Id == HierarchyId.Parse("/1/1/1/")).First();
team.Name = "Team1";
team = db.Organizations.Where(p => p.Id == HierarchyId.Parse("/1/1/2/")).First();
team.Name = "Team2";
db.SaveChanges();
}
//查询分公司1下的所有小组
using (var db = new DemoContext())
{
var organizations= db.Organizations.Where(p => p.Id.GetLevel()==3
&& p.Id.GetAncestor(2)== HierarchyId.Parse("/1/"))
.OrderBy(p=>p.Id).ToList();
foreach (var organization in organizations)
{
Console.WriteLine(@$"{organization.Id} {organization.Name}");
}
}
|
运行成功:
