跳转到主要内容

关系常见问题解答

如何创建自引用关系

自引用关系是指关系与自身建立关联的情况。 当您将实体存储在类似树形结构的数据中时,这非常有用。 同时,“邻接列表”模式也是使用自引用关系来实现的。 例如,您想在应用程序中创建一个分类树。 分类可以嵌套其他分类,嵌套分类可以嵌套其他分类,依此类推。 这时就可以使用自引用关系。 基本上,自引用关系就是将关系定位到实体本身的常规关系。 示例:

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

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

@Column()
title: string

@Column()
text: string

@ManyToOne((type) => Category, (category) => category.childCategories)
parentCategory: Category

@OneToMany((type) => Category, (category) => category.parentCategory)
childCategories: Category[]
}

如何在不连接关系的情况下使用关系 ID

有时候,您希望在对象中获取相关对象的 ID,而无需加载它。 例如:

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

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

@Column()
gender: string

@Column()
photo: string
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from "typeorm"
import { Profile } from "./Profile"

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

@Column()
name: string

@OneToOne((type) => Profile)
@JoinColumn()
profile: Profile
}

当您加载用户时,如果没有连接profile,您的用户对象中将没有关于配置文件的任何信息, 甚至没有配置文件 ID:

User {
id: 1,
name: "Umed"
}

但有时候您想知道此用户的"配置文件 ID",而无需加载该用户的整个配置文件。 为此,您只需向实体添加另一个属性,使用@Column,该属性的名称与关系创建的列完全相同。示例:

import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from "typeorm"
import { Profile } from "./Profile"

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

@Column()
name: string

@Column({ nullable: true })
profileId: number

@OneToOne((type) => Profile)
@JoinColumn()
profile: Profile
}

这样就完成了。下次加载用户对象时,它将包含配置文件 ID:

User {
id: 1,
name: "Umed",
profileId: 1
}

如何在实体中加载关系

加载实体关系的最简单方法是在FindOptions中使用relations选项:

const users = await dataSource.getRepository(User).find({
relations: {
profile: true,
photos: true,
videos: true,
},
})

另一种更灵活的方法是使用QueryBuilder

const user = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.profile", "profile")
.leftJoinAndSelect("user.photos", "photo")
.leftJoinAndSelect("user.videos", "video")
.getMany()

使用QueryBuilder,您可以使用innerJoinAndSelect代替leftJoinAndSelect (有关LEFT JOININNER JOIN之间的区别,请参考您的SQL文档), 您可以通过条件连接关系数据,进行排序等操作。

了解更多关于 QueryBuilder 的信息。

避免关系属性初始化器

有时候初始化关系属性非常有用,例如:

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((type) => Category, (category) => category.questions)
@JoinTable()
categories: Category[] = [] // 在这里看到了 = [] 的初始化
}

然而,在 TypeORM 实体中,这可能会引起问题。 为了理解问题,让我们首先尝试加载未设置初始化器的 Question 实体。 当您加载问题时,它将返回以下对象:

Question {
id: 1,
title: "Question about ..."
}

现在,当您保存该对象时,其中的 categories 不会被操作,因为它是未设置的。

但是,如果有一个初始化器,加载的对象将如下所示:

Question {
id: 1,
title: "Question about ...",
categories: []
}

当您保存该对象时,它将检查数据库中是否有与该问题绑定的任何类别 - 并且它将分离所有这些类别。为什么?因为等于 [] 或其中的任何项的关系将被视为 从中删除了某些内容,没有其他方法来检查实体是否从实体中删除。

因此,保存这样的对象将导致问题 - 它将删除所有先前设置的类别。

如何避免这种行为?简单地不要在实体中初始化数组。 相同的规则也适用于构造函数 - 也不要在构造函数中初始化它。

避免创建外键约束

有时出于性能原因,您可能希望在实体之间建立关系,但不创建外键约束。 您可以使用createForeignKeyConstraints选项(默认值为true)定义是否应创建外键约束。

import { Entity, PrimaryColumn, Column, ManyToOne } from "typeorm"
import { Person } from "./Person"

@Entity()
export class ActionLog {
@PrimaryColumn()
id: number

@Column()
date: Date

@Column()
action: string

@ManyToOne((type) => Person, {
createForeignKeyConstraints: false,
})
person: Person
}

避免循环导入错误

以下是一个示例,如果您想定义实体,并且不希望在某些环境中导致错误。 在这种情况下,我们有一个 Action.ts 文件和一个 Person.ts 文件,它们相互导入以建立多对多的关系。 我们使用 import type 语法,这样我们就可以使用类型信息而不生成任何 JavaScript 代码。

import { Entity, PrimaryColumn, Column, ManytoMany } from "typeorm"
import type { Person } from "./Person"

@Entity()
export class ActionLog {
@PrimaryColumn()
id: number

@Column()
date: Date

@Column()
action: string

@ManyToMany("Person", (person: Person) => person.id)
person: Person
}
import { Entity, PrimaryColumn, ManytoMany } from "typeorm"
import type { ActionLog } from "./Action"

@Entity()
export class Person {
@PrimaryColumn()
id: number

@ManyToMany("ActionLog", (actionLog: ActionLog) => actionLog.id)
log: ActionLog
}

通过使用 import type,您可以仅导入类型而避免循环依赖问题。这样可以在代码中引用类型信息,而不会导致在某些环境中出现导入错误。