关联关系
本指南是 Lucid 关联关系的概念性入口。你将了解:
- Lucid 中的关联关系是什么以及如何声明
- Lucid 如何命名关联关系以及它们在代码中的位置
- 可用的关联关系类型,包括每种类型的数据库布局和模型代码
每种关联关系类型的详细加载、查询和写入 API 位于页面底部链接的各关联关系指南中。
概述
关联关系通过共享列将一个模型连接到另一个模型。你在模型类上声明一次关联关系,然后在代码中遍历它们而无需手动编写连接。
import { hasMany } from '@adonisjs/lucid/orm'
import type { HasMany } from '@adonisjs/lucid/types/relations'
import { UsersSchema } from '#database/schema'
import Post from '#models/post'
export default class User extends UsersSchema {
@hasMany(() => Post)
declare posts: HasMany<typeof Post>
}
import { belongsTo } from '@adonisjs/lucid/orm'
import type { BelongsTo } from '@adonisjs/lucid/types/relations'
import { PostsSchema } from '#database/schema'
import User from '#models/user'
export default class Post extends PostsSchema {
@belongsTo(() => User)
declare author: BelongsTo<typeof User>
}
现在两侧已连接。你可以从任一方向遍历关联关系。
const user = await User.query().preload('posts').firstOrFail()
console.log(user.posts)
const post = await Post.query().preload('author').firstOrFail()
console.log(post.author.email)
Lucid 如何表达关联关系
一些约定适用于你声明的每个关联关系。
关联关系存在于模型上
关联关系在 app/models/ 内的模型类中声明。它们不是生成的 schema 类的一部分。重新生成 database/schema.ts 永远不会触及你的关联关系。
方向性命名
关联关系根据哪一侧持有外键来命名,而不是根据行数基数。
- 你的模型持有外键。使用
belongsTo。 - 另一个模型的外键指向你,且最多只有一行。使用
hasOne。 - 另一个模型的外键指向你,且可以有多行。使用
hasMany。 - 两个模型共享一个持有双方外键的中间表。使用
manyToMany。 - 你通过中间模型到达目标模型。使用
hasManyThrough。
这种方向性风格从两侧读起来都一致。Post 属于一个 User,User 有多个 Post。外键位于何处没有歧义。
基于函数的关联模型引用
关联模型作为返回类的函数传入。
@hasMany(() => Post)
declare posts: HasMany<typeof Post>
该函数将 Post 的解析延迟到每个模块加载之后,这避免了两个模型相互引用时的循环导入错误。
覆盖位于装饰器上
当 Lucid 的默认值与你的 schema 不匹配时,每个装饰器都接受一个 options 对象用于关键覆盖。
@belongsTo(() => User, { foreignKey: 'authorId' })
declare author: BelongsTo<typeof User>
每个装饰器上的显式覆盖是自定义键的规范方式。它们是可搜索的,并使每个关联关系自文档化。
关联关系类型
每种可用关联关系类型的迁移和模型结构。
BelongsTo
一篇文章属于一个用户。拥有模型持有一个指向关联模型主键的外键列。
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
async up() {
this.schema.createTable('posts', (table) => {
table.increments('id')
table.string('title').notNullable()
table.text('body')
table
.integer('user_id')
.unsigned()
.notNullable()
.references('users.id')
.onDelete('CASCADE')
table.timestamps(true, true)
})
}
}
import { belongsTo } from '@adonisjs/lucid/orm'
import type { BelongsTo } from '@adonisjs/lucid/types/relations'
import { PostsSchema } from '#database/schema'
import User from '#models/user'
export default class Post extends PostsSchema {
@belongsTo(() => User)
declare author: BelongsTo<typeof User>
}
HasOne
一个用户有一个个人资料。关联模型持有一个指向父记录主键的外键列,唯一索引确保每个父记录只有一行。
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
async up() {
this.schema.createTable('profiles', (table) => {
table.increments('id')
table
.integer('user_id')
.unsigned()
.notNullable()
.unique()
.references('users.id')
.onDelete('CASCADE')
table.string('display_name')
table.text('bio')
table.timestamps(true, true)
})
}
}
import { hasOne } from '@adonisjs/lucid/orm'
import type { HasOne } from '@adonisjs/lucid/types/relations'
import { UsersSchema } from '#database/schema'
import Profile from '#models/profile'
export default class User extends UsersSchema {
@hasOne(() => Profile)
declare profile: HasOne<typeof Profile>
}
HasMany
一个用户有多篇文章。关联模型持有一个指向父记录主键的外键列,没有唯一性约束。
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
async up() {
this.schema.createTable('posts', (table) => {
table.increments('id')
table.string('title').notNullable()
table.text('body')
table
.integer('user_id')
.unsigned()
.notNullable()
.references('users.id')
.onDelete('CASCADE')
table.timestamps(true, true)
})
}
}
import { hasMany } from '@adonisjs/lucid/orm'
import type { HasMany } from '@adonisjs/lucid/types/relations'
import { UsersSchema } from '#database/schema'
import Post from '#models/post'
export default class User extends UsersSchema {
@hasMany(() => Post)
declare posts: HasMany<typeof Post>
}
ManyToMany
一个用户有多个技能,每个技能属于多个用户。一个中间表持有双方的外键。两个模型都不直接持有对方的外键。
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
async up() {
this.schema.createTable('user_skills', (table) => {
table.increments('id')
table.integer('user_id').unsigned().notNullable().references('users.id').onDelete('CASCADE')
table.integer('skill_id').unsigned().notNullable().references('skills.id').onDelete('CASCADE')
table.unique(['user_id', 'skill_id'])
table.timestamps(true, true)
})
}
}
import { manyToMany } from '@adonisjs/lucid/orm'
import type { ManyToMany } from '@adonisjs/lucid/types/relations'
import { UsersSchema } from '#database/schema'
import Skill from '#models/skill'
export default class User extends UsersSchema {
@manyToMany(() => Skill)
declare skills: ManyToMany<typeof Skill>
}
HasManyThrough
一个国家通过其用户有多篇文章。中间模型持有指向父记录的外键,关联模型持有指向中间模型的外键。
table
.integer('country_id')
.unsigned()
.notNullable()
.references('countries.id')
.onDelete('CASCADE')
table
.integer('user_id')
.unsigned()
.notNullable()
.references('users.id')
.onDelete('CASCADE')
import { hasManyThrough } from '@adonisjs/lucid/orm'
import type { HasManyThrough } from '@adonisjs/lucid/types/relations'
import { CountriesSchema } from '#database/schema'
import Post from '#models/post'
import User from '#models/user'
export default class Country extends CountriesSchema {
@hasManyThrough([() => Post, () => User])
declare posts: HasManyThrough<typeof Post>
}
后续步骤
每种关联关系类型都有自己的指南,涵盖加载、查询、持久化以及完整的选项参考,包括外键覆盖和 onQuery 钩子。
- BelongsTo 用于一对一和多对一关联关系的拥有侧
- HasMany 用于从被引用侧的一对多
- ManyToMany 用于基于中间表的多对多,包括中间表列、时间戳以及 attach、detach 和 sync API
- HasOne 用于从被引用侧的一对一
- HasManyThrough 用于遍历中间模型