跳至主内容区

树形实体

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

TypeORM 支持使用邻接列表和闭包表模式存储树形结构。 要深入了解分层数据表,请查看 Bill Karwin 的精彩演示

邻接列表

邻接列表是通过自引用来实现的简单模型。 请注意 TreeRepository 不支持邻接列表模式。 这种方法的优势在于简单易用, 缺点是受连接操作限制,无法一次性加载大型树结构。 要了解邻接列表的更多优势和使用场景,请参阅 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[]
}

嵌套集

嵌套集是另一种存储树形结构的数据库模式。 该模式读取效率极高,但写入性能较差。 嵌套集不支持多个根节点。 示例:

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
}

物化路径(路径枚举)

物化路径(也称为路径枚举)是另一种存储树形结构的数据库模式。 该方法简单且高效。 示例:

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
}

闭包表

闭包表通过特殊方式在独立表中存储父子关系。 该模式在读取和写入操作中都具有高效性。 示例:

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
}

您可以通过在 @Tree("closure-table", options) 中设置可选参数 options 来指定闭包表名称和/或列名。ancestorColumnNamedescandantColumnName 是回调函数,它们接收主列的元数据并返回列名称。

@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()
// returns root categories with sub categories inside

const treeCategoriesWithLimitedDepth = await dataSource.manager
.getTreeRepository(Category)
.findTrees({ depth: 2 })
// returns root categories with sub categories inside, up to depth 2
  • findRoots - 根节点指没有祖先的实体。该方法查找所有根节点,但不会加载其叶子节点。
const rootCategories = await dataSource.manager
.getTreeRepository(Category)
.findRoots()
// returns root categories without sub categories inside
  • findDescendants - 获取指定实体的所有后代子节点,以扁平数组形式返回。
const children = await dataSource.manager
.getTreeRepository(Category)
.findDescendants(parentCategory)
// returns all direct subcategories (without its nested categories) of a parentCategory
  • findDescendantsTree - 获取指定实体的所有后代子节点,以嵌套树形结构返回。
const childrenTree = await repository.findDescendantsTree(parentCategory)
// returns all direct subcategories (with its nested categories) of a parentCategory
const childrenTreeWithLimitedDepth = await repository.findDescendantsTree(
parentCategory,
{ depth: 2 },
)
// returns all direct subcategories (with its nested categories) of a parentCategory, up to depth 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)
// returns all direct childCategory's parent categories (without "parent of parents")
  • findAncestorsTree - 获取指定实体的所有祖先节点,以嵌套树形结构返回。
const parentsTree = await dataSource.manager
.getTreeRepository(Category)
.findAncestorsTree(childCategory)
// returns all direct childCategory's parent categories (with "parent of parents")
  • 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"],
})
// automatically joins the sites relation

const parentsWithRelations = await dataSource.manager
.getTreeRepository(Category)
.findAncestors(childCategory, {
relations: ["members"],
})
// returns all direct childCategory's parent categories (without "parent of parents") and joins the 'members' relation