跳到主要内容

引用操作升级路径

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-m 关系,并在 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 及其消息

“您尝试进行的更改将违反 {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)
})