树实体
TypeORM 支持使用邻接列表(Adjacency list)和闭包表(Closure table)模式存储树形结构。 要了解有关层次表的更多信息,请参阅 Bill Karwin 的演示文稿。
邻接列表(Adjacency list)
邻接列表是一种具有自引用的简单模型。 这种方法的好处是简单,缺点是由于连接限制,无法一次加载大型树。 要了解有关邻接列表的好处和用法,请参阅 Matthew Schinckel 的文章。 示例:
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
OneToMany,
} from "typeorm"
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column()
description: string
@ManyToOne((type) => Category, (category) => category.children)
parent: Category
@OneToMany((type) => Category, (category) => category.parent)
children: Category[]
}
嵌套集合(Nested set)
嵌套集合是在数据库中存储树形结构的另一种模式。 它非常适合读取,但对于写入来说不太好。 嵌套集合中不能有多个根。 示例:
import {
Entity,
Tree,
Column,
PrimaryGeneratedColumn,
TreeChildren,
TreeParent,
TreeLevelColumn,
} from "typeorm"
@Entity()
@Tree("nested-set")
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@TreeChildren()
children: Category[]
@TreeParent()
parent: Category
}
材料化路径(Materialized Path aka Path Enumeration)
材料化路径(也称为路径枚举)是在数据库中存储树形结构的另一种模式。 它简单而有效。 示例:
import {
Entity,
Tree,
Column,
PrimaryGeneratedColumn,
TreeChildren,
TreeParent,
TreeLevelColumn,
} from "typeorm"
@Entity()
@Tree("materialized-path")
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@TreeChildren()
children: Category[]
@TreeParent()
parent: Category
}
闭包表(Closure table)
闭包表以特殊的方式在独立表中存储父子关系。它在读取和写入方面都非常高效。 示例:
import {
Entity,
Tree,
Column,
PrimaryGeneratedColumn,
TreeChildren,
TreeParent,
TreeLevelColumn,
} from "typeorm"
@Entity()
@Tree("closure-table")
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@TreeChildren()
children: Category[]
@TreeParent()
parent: Category
}
您可以通过将可选参数 options
设置到 @Tree("closure-table", options)
中来指定闭包表的名称和/或闭包表列名。ancestorColumnName
和 descandantColumnName
是回调函数,接收主列的元数据并返回列的名称。
@Tree("closure-table", {
closureTableName: "category_closure",
ancestorColumnName: (column) => "ancestor_" + column.propertyName,
descendantColumnName: (column) => "descendant_" + column.propertyName,
})
使用树实体
要将树实体相互绑定,需要在子实体中设置父级,然后保存它们。 例如:
const a1 = new Category()
a1.name = "a1"
await dataSource.manager.save(a1)
const a11 = new Category()
a11.name = "a11"
a11.parent = a1
await dataSource.manager.save(a11)
const a12 = new Category()
a12.name = "a12"
a12.parent = a1
await dataSource.manager.save(a12)
const a111 = new Category()
a111.name = "a111"
a111.parent = a11
await dataSource.manager.save(a111)
const a112 = new Category()
a112.name = "a112"
a112.parent = a11
await dataSource.manager.save(a112)
要加载这样的树,请使用 TreeRepository
:
const trees = await dataSource.manager.getTreeRepository(Category).findTrees()
trees
将如下所示:
[
{
"id": 1,
"name": "a1",
"children": [
{
"id": 2,
"name": "a11",
"children": [
{
"id": 4,
"name": "a111"
},
{
"id": 5,
"name": "a112"
}
]
},
{
"id": 3,
"name": "a12"
}
]
}
]
还有其他通过 TreeRepository
处理树实体的特殊方法:
findTrees
- 返回数据库中的所有树及其所有子级、子级的子级等等。
const treeCategories = await dataSource.manager.getTreeRepository(Category).findTrees()
// 返回包含子类别的根类别
const treeCategoriesWithLimitedDepth = await dataSource.manager.getTreeRepository(Category).findTrees({ depth: 2 })
// 返回包含子类别的根类别,深度限制为 2
findRoots
- 根是没有祖先的实体。找到所有的根。 不加载子级的叶子。
const rootCategories = await dataSource.manager.getTreeRepository(Category).findRoots()
// 返回不包含子类别的根类别
findDescendants
- 获取给定实体的所有子级(后代)。以扁平数组的形式返回它们。
const children = await dataSource.manager.getTreeRepository(Category).findDescendants(parentCategory)
// 返回父类别的所有直接子类别(不包括其嵌套类别)
findDescendantsTree
- 获取给定实体的所有子级(后代)。以树状结构返回它们。
const childrenTree = await repository.findDescendantsTree(parentCategory)
// 返回父类别的所有直接子类别(包括其嵌套类别)
const childrenTreeWithLimitedDepth = await repository.findDescendantsTree(
parentCategory,
{ depth: 2 },
)
// 返回父类别的所有直接子类别(包括其嵌套类别),深度限制为 2
createDescendantsQueryBuilder
- 创建用于获取树中实体的后代的查询构建器。
const children = await repository
.createDescendantsQueryBuilder(
"category",
"categoryClosure",
parentCategory,
)
.andWhere("category.type = 'secondary'")
.getMany()
countDescendants
- 获取实体的后代数量。
const childrenCount = await dataSource.manager.getTreeRepository(Category).countDescendants(parentCategory)
findAncestors
- 获取给定实体的所有父级(祖先)。以扁平数组的形式返回它们。
const parents = await repository.findAncestors(childCategory)
// 返回直接子类别的所有父级类别(不包括 "父级的父级")
findAncestorsTree
- 获取给定实体的所有父级(祖先)。以树状结构返回它们。
const parentsTree = await dataSource.manager.getTreeRepository(Category).findAncestorsTree(childCategory)
// 返回直接子类别的所有父级类别(包括 "父级的父级")
createAncestorsQueryBuilder
- 创建用于获取树中实体的祖先的查询构建器。
const parents = await repository
.createAncestorsQueryBuilder("category", "categoryClosure", childCategory)
.andWhere("category.type = 'secondary'")
.getMany()
countAncestors
- 获取实体的祖先数量。
const parentsCount = await dataSource.manager.getTreeRepository(Category).countAncestors(childCategory)
对于以下方法,可以传递选项:
- findTrees
- findRoots
- findDescendants
- findDescendantsTree
- findAncestors
- findAncestorsTree
可以使用以下选项:
relations
- 指示要加载的实体关系(简化的左连接形式)。
示例:
const treeCategoriesWithRelations = await dataSource.manager.getTreeRepository(Category).findTrees({
relations: ["sites"],
})
// 自动连接 sites 关系
const parentsWithRelations = await dataSource.manager.getTreeRepository(Category).findAncestors(childCategory, {
relations: ["members"],
})
// 返回直接子类别的所有父级类别(不包括 "父级的父级")并连接 'members' 关系