跳到主要内容

引用操作升级路径

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 版本时写入数据库外键的默认引用操作

子句可选关联必要关联
onDeleteSetNullCascade
onUpdateCascadeCascade

除了数据库引用操作之外,Prisma Client 2.x 版本中还强制执行以下操作

子句可选关联必要关联
onDeleteSetNullRestrict
onUpdateCascadeCascade

升级路径

升级时有几种路径可以选择,根据期望的结果会产生不同的效果。

如果你当前使用迁移工作流,可以运行 prisma db pull 来检查默认值如何反映在你的 schema 中。然后,如果需要,可以手动更新数据库。

你也可以选择跳过检查默认值,直接运行迁移以使用新的默认值更新数据库。

使用内省

如果你对数据库进行内省,数据库层面配置的引用操作将反映在你的 Prisma Schema 中。如果你一直使用 Prisma Migrate 或 prisma db push 来管理数据库 schema,这些很可能是 <=2.25.0 的默认值

当你运行内省时,Prisma ORM 会将数据库中的所有外键与 schema 进行比较,如果 SQL 语句 ON DELETEON UPDATE 与默认值匹配,它们将在 schema 文件中被显式设置。

内省之后,你可以检查 schema 中的非默认子句。最重要的子句是 onDelete,在 2.25.0 及更早版本中默认为 Cascade

危险

如果你使用 delete()deleteAll() 方法,现在将执行级联删除,因为 Prisma Client 中以前在运行时阻止级联删除的安全网已被移除。请务必检查你的代码并进行相应的调整。

确保你对 schema 中所有 onDelete: Cascade 的情况都满意。如果不满意,则可以

  • 修改你的 Prisma schema 并运行 db pushdev 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 不再执行运行时检查。你可以改为指定自定义引用操作来保持关联之间的引用完整性。

当你使用 NoActionRestrict 来阻止删除记录时,3.0.1 及更高版本(或启用 referentialActions 功能标志的 2.26.0 版本)中的错误消息将与之前的版本不同。这是因为它们现在是由数据库触发的,而不是 Prisma Client。可以预期的新的错误码是 P2003,因此你应该检查你的代码并进行相应的调整。

捕获错误的示例

以下示例使用下面的博客 schema,其中 PostUser 之间存在 1 对多的关系,并在 author 字段上设置了 Restrict 引用操作。

这意味着如果一个用户有帖子,该用户(及其帖子)无法被删除。

schema.prisma
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,其消息为

"你尝试进行的更改将违反 '{relation_name}' 在 {model_a_name} 和 {model_b_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)
})