跳至主要内容

引用操作

引用操作决定在应用程序删除或更新相关记录时,对记录执行的操作。

从 2.26.0 版本开始,您可以在 Prisma 架构中的关系字段上定义引用操作。这使您能够在 Prisma ORM 级别定义诸如级联删除和级联更新之类的引用操作。

信息

版本差异

  • 如果您使用的是 3.0.1 或更高版本,则可以使用此页面上所述的引用操作。
  • 如果您使用的是 2.26.0 到 3.0.0 之间的版本,则可以使用此页面上所述的引用操作,但您必须启用预览功能标志referentialActions
  • 如果您使用的是 2.25.0 或更早版本,则可以在数据库中手动配置级联删除。

在以下示例中,在 Post 模型的 author 字段中添加 onDelete: Cascade 表示删除 User 记录也将删除所有相关的 Post 记录。

schema.prisma
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 ORM 将使用默认值

危险

如果您从 2.26.0 之前的版本升级:您必须检查引用操作的升级路径部分。Prisma ORM 对引用操作的支持会移除 Prisma Client 中防止在运行时进行级联删除的安全网。如果您在未升级数据库的情况下使用此功能,则旧的默认操作 - ON DELETE CASCADE - 会变为活动状态。这可能会导致您意想不到的级联删除。

什么是引用操作?

引用操作是策略,定义在您运行updatedelete查询时,数据库如何处理引用的记录。

数据库级别的引用操作

引用操作是外键约束的功能,外键约束存在于您的数据库中以维护引用完整性。

当您在 Prisma 架构中定义数据模型之间的关系时,您使用关系字段它们在数据库中不存在,以及标量字段它们在数据库中存在。这些外键在数据库级别连接模型。

引用完整性规定这些外键必须引用相关数据库表中存在的现有主键值。在您的 Prisma 架构中,这通常由相关模型上的 id 字段表示。

默认情况下,数据库将拒绝任何违反引用完整性的操作,例如删除引用的记录。

如何使用引用操作

引用操作在@relation属性中定义,并映射到底层数据库中外键约束上的操作。如果您未指定引用操作,Prisma ORM 将回退到默认值

以下模型定义了 UserPost 之间的一对多关系,以及 PostTag 之间的多对多关系,并带有明确定义的引用操作

schema.prisma
model User {
id Int @id @default(autoincrement())
posts Post[]
}

model Post {
id Int @id @default(autoincrement())
title String
tags TagOnPosts[]
User User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
userId Int?
}

model TagOnPosts {
id Int @id @default(autoincrement())
post Post? @relation(fields: [postId], references: [id], onUpdate: Cascade, onDelete: Cascade)
tag Tag? @relation(fields: [tagId], references: [id], onUpdate: Cascade, onDelete: Cascade)
postId Int?
tagId Int?
}

model Tag {
id Int @id @default(autoincrement())
name String @unique
posts TagOnPosts[]
}

此模型明确定义了以下引用操作

  • 如果您删除了 Tag,则使用 Cascade 引用操作,相应的标签分配也会从 TagOnPosts 中删除
  • 如果您删除了 User,则由于 SetNull 引用操作,作者将从所有帖子中移除,将字段值设置为 Null。要允许这样做,UseruserId 必须是 Post 中的可选字段。

Prisma ORM 支持以下引用操作

引用操作默认值

如果您未指定引用操作,Prisma ORM 将使用以下默认值

子句可选关系强制关系
onDelete设置为 null限制
onUpdate级联级联

例如,在以下架构中,所有 Post 记录都必须通过 author 关系连接到 User

model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}

model User {
id Int @id @default(autoincrement())
posts Post[]
}

该架构没有在强制 author 关系字段上明确定义引用操作,这意味着将应用 onDelete 的默认引用操作 RestrictonUpdate 的默认引用操作 Cascade

注意事项

以下注意事项适用

  • 引用操作不支持隐式多对多关系。要使用引用操作,您必须定义显式多对多关系,并在联接表上定义引用操作。
  • 某些引用操作和必需/可选关系的组合不兼容。例如,在必需关系上使用 SetNull 会在删除引用记录时导致数据库错误,因为非空约束将被违反。查看此 GitHub 问题以获取更多信息。

引用操作的类型

下表显示了每个数据库支持的引用操作。

数据库级联限制无操作设置为 null设置为默认值
PostgreSQL✔️✔️✔️✔️⌘✔️
MySQL/MariaDB✔️✔️✔️✔️❌ (✔️†)
SQLite✔️✔️✔️✔️✔️
SQL Server✔️❌‡✔️✔️✔️
CockroachDB✔️✔️✔️✔️✔️
MongoDB††✔️✔️✔️✔️

引用操作的特殊情况

引用操作是 ANSI SQL 标准的一部分。但是,在某些情况下,一些关系数据库会偏离标准。

MySQL/MariaDB

MySQL/MariaDB 和底层的 InnoDB 存储引擎不支持 SetDefault。确切的行为取决于数据库版本

  • 在 MySQL 8 及更高版本和 MariaDB 10.5 及更高版本中,SetDefault 实际上充当 NoAction 的别名。您可以使用 SET DEFAULT 引用操作定义表,但在运行时会触发外键约束错误。
  • 在 MySQL 5.6 及更高版本和 MariaDB 10.5 之前的版本中,尝试使用 SET DEFAULT 引用操作创建表定义会因语法错误而失败。

因此,当您将 mysql 设置为数据库提供程序时,Prisma ORM 会警告用户将 Prisma 架构中的 SetDefault 引用操作替换为其他操作。

PostgreSQL

PostgreSQL 是 Prisma ORM 支持的唯一数据库,允许您定义引用非空字段的 SetNull 引用操作。但是,当在运行时触发操作时,这会引发外键约束错误。

因此,当您在(默认)foreignKeys 关系模式下将 postgres 设置为数据库提供程序时,Prisma ORM 会警告用户将包含在带有 SetNull 引用操作的 @relation 属性中的任何字段标记为可选。对于所有其他数据库提供程序,Prisma ORM 会使用验证错误拒绝模式。

SQL Server

Restrict 不适用于 SQL Server 数据库,但您可以使用 NoAction 代替。

Cascade

  • onDelete: Cascade 删除引用记录将触发引用记录的删除。
  • onUpdate: Cascade 如果依赖记录的引用标量字段更新,则更新关系标量字段。

示例用法

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
authorId Int
}

model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 Cascade 的结果

如果删除了 User 记录,那么他们的帖子也会被删除。如果用户的 id 更新,则相应的 authorId 也会更新。

如何使用级联删除

Restrict

  • onDelete: Restrict 如果存在任何引用记录,则阻止删除。
  • onUpdate: Restrict 阻止更改引用记录的标识符。

示例用法

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Restrict, onUpdate: Restrict)
authorId Int
}

model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 Restrict 的结果

具有帖子的 User 无法删除。Userid 无法更改。

警告

Restrict 操作在 Microsoft SQL Server不可用,并会触发模式验证错误。您可以使用 NoAction 代替,它会产生相同的结果,并且与 SQL Server 兼容。

NoAction

NoAction 操作类似于 Restrict,两者之间的区别取决于所使用的数据库。

  • PostgreSQLNoAction 允许将检查(如果表上存在引用行)延迟到事务的后期。有关更多信息,请参阅 PostgreSQL 文档
  • MySQLNoAction 的行为与 Restrict 完全相同。有关更多信息,请参阅 MySQL 文档
  • SQLite:当相关的主键修改或删除时,不会执行任何操作。有关更多信息,请参阅 SQLite 文档
  • SQL Server:当删除或修改引用记录时,将引发错误。有关更多信息,请参阅 SQL Server 文档
  • MongoDB(从 3.6.0 版本开始预览):当修改或删除记录时,不会对任何相关记录执行任何操作。
警告

如果您使用 在 Prisma Client 中管理关系 而不是在数据库中使用外键,则应注意,目前 Prisma ORM 仅实现引用操作。外键还创建约束,这使得无法以违反这些约束的方式操作数据:数据库不会执行查询,而是返回错误。如果您在 Prisma Client 中模拟引用完整性,这些约束将不会创建,因此如果您将引用操作设置为 NoAction,将不会有检查来阻止您破坏引用完整性。

示例用法

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
authorId Int
}

model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 NoAction 的结果

具有帖子的 User 无法删除。Userid 无法更改。

SetNull

  • onDelete: SetNull 引用对象的标量字段将设置为 NULL

  • onUpdate: SetNull 当更新引用对象的标识符时,引用对象的标量字段将设置为 NULL

SetNull 仅适用于可选关系。在必需关系上,将抛出运行时错误,因为标量字段不能为 null。

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
author User? @relation(fields: [authorId], references: [id], onDelete: SetNull, onUpdate: SetNull)
authorId Int?
}

model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 SetNull 的结果

删除 User 时,其所有已发布帖子的 authorId 将设置为 NULL

更改 Userid 时,其所有已发布帖子的 authorId 将设置为 NULL

SetDefault

  • onDelete: SetDefault 引用对象的标量字段将设置为字段的默认值。

  • onUpdate: SetDefault 引用对象的标量字段将设置为字段的默认值。

这些要求使用 @default 为关系标量字段设置默认值。如果任何标量字段都没有提供默认值,将抛出运行时错误。

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
authorUsername String? @default("anonymous")
author User? @relation(fields: [authorUsername], references: [username], onDelete: SetDefault, onUpdate: SetDefault)
}

model User {
username String @id
posts Post[]
}
使用 SetDefault 的结果

删除 User 时,其现有帖子的 authorUsername 字段值将设置为 'anonymous'。

更改 Userusername 时,其现有帖子的 authorUsername 字段值将设置为 'anonymous'。

特定于数据库的要求

如果您在数据模型中具有 自关系循环关系,则 MongoDB 和 SQL Server 对引用操作有特定要求。如果您具有带有 多个级联路径 的关系,SQL Server 也有特定要求。

从 2.25.0 及更早版本升级的路径

升级时您可以采取几种路径,这将根据所需的结果产生不同的结果。

如果您当前使用迁移工作流程,则可以运行内省以检查默认值如何在模式中反映。如果您需要,可以手动更新数据库。

您也可以决定跳过检查默认值,并运行迁移以使用 新的默认值 更新数据库。

以下假设您已升级到 2.26.0 或更高版本,并启用了预览功能标志,或者已升级到 3.0.0 或更高版本

使用内省

如果您 内省 数据库,则在数据库级别配置的引用操作将反映在您的 Prisma 模式中。如果您一直使用 Prisma Migrate 或 prisma db push 来管理数据库模式,这些很可能是 2.25.0 及更早版本中的默认值

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

内省后,您可以查看模式中的非默认子句。最重要的是查看 onDelete 子句,该子句在 2.25.0 及更早版本中默认为 Cascade

警告

如果您使用的是 delete()deleteMany() 方法,级联删除 现在将执行,因为 referentialActions 预览功能删除了 Prisma Client 中之前阻止在运行时进行级联删除的安全网。请务必检查您的代码并根据需要进行任何调整。

确保您对模式中每个 onDelete: Cascade 实例都感到满意。如果没有,请执行以下操作:

  • 修改 Prisma 模式并 db pushdev migrate 以更改数据库

  • 如果您使用的是仅内省工作流程,请手动更新底层数据库

以下示例将导致级联删除,如果删除了 User,那么其所有 Post 也将被删除。

博客模式示例

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 命令)时,新的默认值 将应用到您的数据库。

信息

与您首次运行 Introspect 不同,新的引用操作子句和属性 **不会** 由 Prisma VSCode 扩展自动添加到您的 Prisma 架构中。如果您想使用除新的默认值以外的任何内容,您需要手动添加它们。

在您的 Prisma 架构中显式定义引用操作是可选的。如果您没有为关系显式定义引用操作,Prisma ORM 将使用 新的默认值

请注意,引用操作可以逐个案例添加。这意味着您可以将它们添加到单个关系中,而无需手动指定任何内容,让其他关系保持默认值。

检查错误

**在** 升级到 2.26.0 并启用引用操作 **预览功能** 之前,Prisma ORM 会在使用 delete()deleteMany() 时阻止删除记录,以维护引用完整性。Prisma Client 会抛出自定义运行时错误,错误代码为 P2014

**在** 升级并启用引用操作 **预览功能** 之后,Prisma ORM 不再执行运行时检查。相反,您可以指定自定义引用操作来维护关系之间的引用完整性。

当您使用 NoActionRestrict 来阻止删除记录时,2.26.0 之后的错误消息将与 2.26.0 之前的错误消息不同。这是因为它们现在是由数据库而不是 Prisma Client 触发的。预期的新错误代码为 P2003

为了确保您捕获这些新错误,您可以相应地调整代码。

捕获错误的示例

以下示例使用下面的博客架构,其中 PostUser 之间存在一对多关系,并在 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()

为了确保您在代码中检查正确的错误,请修改检查以查找 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()