参照操作升级路径
Prisma ORM 2.x 版本在某些 Prisma Client 函数中阻止删除连接的记录,并且不允许你在 Prisma Schema 中配置参照操作来改变这种行为。
Prisma ORM 3.x 及更高版本允许你通过在模型的关系上明确设置参照操作来控制删除或更新记录时发生的情况。升级后,Prisma Client 将不再强制执行任何参照操作,并且写入数据库外键的任何操作都将定义删除或更新记录时的行为。
Prisma Migrate 3.x 在向数据库写入外键约束时,会将 Prisma Client 之前执行的操作作为新的默认值。
Prisma ORM 2.x 行为
当在必需关系上使用 Prisma Client 调用 delete()
或 deleteAll()
方法时,会执行运行时检查,如果记录引用了相关对象,则阻止删除这些记录。这会阻止级联行为,无论外键如何定义。
Prisma ORM 2 中,在不升级的情况下,根本不允许设置参照操作。请参见 Prisma ORM 2.x 默认参照操作
如果你确实需要使用数据库中配置的级联行为,你可以使用raw
SQL 查询来删除多个被引用的记录。这是因为 Prisma Client **不会**对原始查询执行运行时检查。
Prisma ORM 2.x 默认参照操作
以下是使用 Prisma Migrate 2.x 版本时写入数据库外键的默认参照操作
子句 | 可选关系 | 强制关系 |
---|---|---|
onDelete | SetNull | Cascade |
onUpdate | Cascade | Cascade |
除了数据库参照操作之外,Prisma Client 2.x 版本还强制执行以下操作
子句 | 可选关系 | 强制关系 |
---|---|---|
onDelete | SetNull | Restrict |
onUpdate | Cascade | Cascade |
升级路径
升级时有几种路径可以选择,根据期望的结果会产生不同的效果。
如果你当前使用迁移工作流程,可以运行 prisma db pull
来检查默认值如何反映在你的 schema 中。如果需要,你可以手动更新数据库。
你也可以选择跳过检查默认值,并运行迁移来使用新的默认值更新数据库。
使用内省
如果你内省你的数据库,数据库级别配置的参照操作将反映在你的 Prisma Schema 中。如果你一直使用 Prisma Migrate 或 prisma db push
来管理数据库 schema,这些很可能是 <=2.25.0 的默认值。
当你运行内省时,Prisma ORM 会比较数据库中所有的外键与 schema,如果 SQL 语句 ON DELETE
和 ON UPDATE
与默认值**不**匹配,它们将被明确设置在 schema 文件中。
内省后,你可以检查 schema 中的非默认子句。最重要的子句是 onDelete
,在 2.25.0 及更早版本中,它默认为 Cascade
。
如果你正在使用 delete()
或 deleteAll()
方法,**现在将执行级联删除,因为 Prisma Client 中之前在运行时阻止级联删除的安全网已被移除**。请务必检查你的代码并进行相应的调整。
确保你对 schema 中所有 onDelete: Cascade
的情况都满意。如果不是,则:
- 修改你的 Prisma schema 并运行
db push
或dev migrate
来更改数据库,或者 - 如果你的工作流程中只使用
prisma db pull
,则手动更新底层数据库
以下示例将导致级联删除,这意味着如果 User
被删除,那么该用户所有的 Post
也会被删除。
博客 schema 示例
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用迁移
运行迁移(或prisma db push
命令)时,新默认值将应用于你的数据库。
与首次运行 prisma db pull
不同,Prisma VSCode 扩展**不会**自动将新的参照操作子句和属性添加到你的 Prisma schema 中。如果你希望使用新默认值以外的任何值,则必须手动添加它们。
在 Prisma schema 中明确定义参照操作是可选的。如果你没有为关系明确定义参照操作,Prisma ORM 将使用新默认值。
请注意,参照操作可以按需添加。这意味着你可以将它们添加到单个关系中,而其余的则保持默认值,无需手动指定任何内容。
检查错误
在升级到 3.0.1 版本(或启用了 referentialActions
功能标志的 2.26.0 及更高版本)**之前**,Prisma ORM 在使用 delete()
或 deleteMany()
时会阻止删除记录以保持参照完整性。Prisma Client 会抛出一个带有错误代码 P2014
的自定义运行时错误。
升级**之后**,Prisma ORM 不再执行运行时检查。你可以改为指定自定义参照操作以保持关系之间的参照完整性。
当你使用 NoAction
或 Restrict
来阻止记录删除时,3.0.1 及更高版本(或启用了 referentialActions
功能标志的 2.26.0 版本)中的错误消息将与之前的版本不同。这是因为它们现在由数据库**而非** Prisma Client 触发。可以预期的新错误代码是 P2003
,因此你应该检查你的代码并进行相应调整。
错误捕获示例
以下示例使用下面的博客 schema,其中 Post
和 User
之间存在一对多关系,并在 author
字段上设置了 Restrict
参照操作。
这意味着如果一个用户有帖子,那么该用户(及其帖子)**不能**被删除。
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Restrict)
authorId String
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
在升级之前,当你尝试删除一个有帖子的用户时,会收到错误代码 P2014
,其消息为:
"你尝试进行的更改将违反 {model_a_name} 和 {model_b_name} 模型之间所需的 '{relation_name}' 关系。"
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
try {
await prisma.user.delete({
where: {
id: 'some-long-id',
},
})
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2014') {
console.log(error.message)
}
}
}
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
为了确保你在代码中检查正确的错误,请修改你的检查以查找 P2003
,它将返回消息:
"字段上的外键约束失败: {field_name}"
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
try {
await prisma.user.delete({
where: {
id: 'some-long-id'
}
})
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2014') {
if (error.code === 'P2003') {
console.log(error.message)
}
}
}
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})