跳转到主要内容

多对多关系

什么是多对多关系

多对多关系是指A包含多个B实例,而B也包含多个A实例的关系。 我们以Question(问题)和Category(分类)实体为例。 一个问题可以有多个分类,而每个分类可以属于多个问题。

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"

@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number

@Column()
title: string

@Column()
text: string

@ManyToMany(() => Category)
@JoinTable()
categories: Category[]
}

@JoinTable() 是在 @ManyToMany 关系中必须的。 您必须将 @JoinTable 放在关系的一方(拥有方)上。

这个示例会生成以下表格:

+-------------+--------------+----------------------------+
| category |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
| question |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| title | varchar(255) | |
| text | varchar(255) | |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
| question_categories_category |
+-------------+--------------+----------------------------+
| questionId | int(11) | PRIMARY KEY FOREIGN KEY |
| categoryId | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+

保存多对多关系

如果启用了 级联操作,您只需调用一次 save 方法即可保存这个关系。

const category1 = new Category()
category1.name = "animals"
await dataSource.manager.save(category1)

const category2 = new Category()
category2.name = "zoo"
await dataSource.manager.save(category2)

const question = new Question()
question.title = "dogs"
question.text = "who let the dogs out?"
question.categories = [category1, category2]
await dataSource.manager.save(question)

删除多对多关系

如果启用了 级联操作,您只需调用一次 save 方法即可删除这个关系。

要删除两条记录之间的多对多关系,请将其从相应字段中删除,并保存记录。

const question = await dataSource.getRepository(Question).findOne({
relations: {
categories: true,
},
where: { id: 1 }
})
question.categories = question.categories.filter((category) => {
return category.id !== categoryToRemove.id
})
await dataSource.manager.save(question)

这将只删除连接表中的记录。questioncategoryToRemove 记录仍然存在。

级联软删除关系

以下示例展示了级联软删除的行为:

const category1 = new Category()
category1.name = "animals"

const category2 = new Category()
category2.name = "zoo"

const question = new Question()
question.categories = [category1, category2]
const newQuestion = await dataSource.manager.save(question)

await dataSource.manager.softRemove(newQuestion)

在此示例中,我们没有对 category1 和 category2 调用 save 或 softRemove,但是当关系的级联选项设置为 true 时,它们将被自动保存和软删除,如下所示:

import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"

@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number

@ManyToMany(() => Category, (category) => category.questions, {
cascade: true,
})
@JoinTable()
categories: Category[]
}

加载多对多关系

要在加载问题时包含分类,请在 FindOptions 中指定关系:

const questionRepository = dataSource.getRepository(Question)
const questions = await questionRepository.find({
relations: {
categories: true,
},
})

或者使用 QueryBuilder 进行连接:

const questions = await dataSource
.getRepository(Question)
.createQueryBuilder("question")
.leftJoinAndSelect("question.categories", "category")
.getMany()

使用启用了 eager loading 的关系,您不必在查询命令中指定关系,因为它将始终自动加载。如果您使用 QueryBuilder,eager 关系将被禁用,您必须使用 leftJoinAndSelect 来加载关系。

双向关系

关系可以是单向的也可以是双向的。 单向关系是指只在一个方向上有关系装饰器的关系。 双向关系是指在关系的两个方向上都有装饰器的关系。

我们刚刚创建了一个单向关系。让我们把它改为双向关系:

import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from "typeorm"
import { Question } from "./Question"

@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@ManyToMany(() => Question, (question) => question.categories)
questions: Question[]
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"

@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number

@Column()
title: string

@Column()
text: string

@ManyToMany(() => Category, (category) => category.questions)
@JoinTable()
categories: Category[]
}

我们刚刚使关系成为双向关系。请注意,反向关系没有 @JoinTable@JoinTable 只能在关系的一侧使用。

双向关系允许您使用 QueryBuilder 从两个方向连接关系:

const categoriesWithQuestions = await dataSource
.getRepository(Category)
.createQueryBuilder("category")
.leftJoinAndSelect("category.questions", "question")
.getMany()

带有自定义属性的多对多关系

如果您需要在多对多关系中具有其他属性,则需要自己创建一个新的实体。 例如,如果您希望实体 PostCategory 具有带有额外的 order 列的多对多关系,那么您需要创建一个名为 PostToCategory 的实体,其中包含两个方向的 ManyToOne 关系,并且其中有自定义的列:

import { Entity, Column, ManyToOne, PrimaryGeneratedColumn } from "typeorm"
import { Post } from "./post"
import { Category } from "./category"

@Entity()
export class PostToCategory {
@PrimaryGeneratedColumn()
public postToCategoryId: number

@Column()
public postId: number

@Column()
public categoryId: number

@Column()
public order: number

@ManyToOne(() => Post, (post) => post.postToCategories)
public post: Post

@ManyToOne(() => Category, (category) => category.postToCategories)
public category: Category
}

此外,您还需要在 PostCategory 中添加以下关系:

// category.ts
...
@OneToMany(() => PostToCategory, postToCategory => postToCategory.category)
public postToCategories: PostToCategory[];

// post.ts
...
@OneToMany(() => PostToCategory, postToCategory => postToCategory.post)
public postToCategories: PostToCategory[];