关系常见问题解答
如何创建自引用关系
自引用关系是指关系与自身建立关联的情况。 当您将实体存储在类似树形结构的数据中时,这非常有用。 同时,“邻接列表”模式也是使用自引用关系来实现的。 例如,您想在应用程序中创建一个分类树。 分类可以嵌套其他分类,嵌套分类可以嵌套其他分类,依此类推。 这时就可以使用自引用关系。 基本上,自引用关系就是将关系定位到实体本身的常规关系。 示例:
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 JOIN
和INNER 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
,您可以仅导入类型而避免循环依赖问题。这样可以在代码中引用类型信息,而不会导致在某些环境中出现导入错误。