多对多关系
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
什么是多对多关系?
多对多关系是指:实体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[]
}
在@ManyToMany关系中必须使用@JoinTable()装饰器,
且只能将@JoinTable放在关系的拥有方(owning side)。
此示例将生成以下表结构:
+-------------+--------------+----------------------------+
| category |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| question |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| title | varchar(255) | |
| text | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| question_categories_category |
+-------------+--------------+----------------------------+
| questionId | int | PRIMARY KEY FOREIGN KEY |
| categoryId | int | 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)
此操作仅删除联结表中的记录,
question和categoryToRemove记录本身仍然存在。
使用级联进行关系软删除
此示例演示级联软删除的行为:
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 时预加载功能被禁用,必须通过 leftJoinAndSelect 手动加载关系。
双向关系
关系可分为单向(unidirectional)和双向(bidirectional)。 单向关系仅在单侧设置关系装饰器, 双向关系则在两侧都设置关系装饰器。
前文创建的是单向关系,现将其改为双向:
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()
带自定义属性的多对多关系
如需在多对多关系中添加额外属性,
需要自行创建新的实体。
例如:若要在Question和Category间建立
带order(排序)列的多对多关系,
需创建QuestionToCategory实体,
包含两个ManyToOne关系,分别指向两个实体,以及自定义字段:
import { Entity, Column, ManyToOne, PrimaryGeneratedColumn } from "typeorm"
import { Question } from "./question"
import { Category } from "./category"
@Entity()
export class QuestionToCategory {
@PrimaryGeneratedColumn()
public questionToCategoryId: number
@Column()
public questionId: number
@Column()
public categoryId: number
@Column()
public order: number
@ManyToOne(() => Question, (question) => question.questionToCategories)
public question: Question
@ManyToOne(() => Category, (category) => category.questionToCategories)
public category: Category
}
同时需要在Question和Category实体中
添加如下关联关系:
// category.ts
...
@OneToMany(() => QuestionToCategory, questionToCategory => questionToCategory.category)
public questionToCategories: QuestionToCategory[];
// question.ts
...
@OneToMany(() => QuestionToCategory, questionToCategory => questionToCategory.question)
public questionToCategories: QuestionToCategory[];