跳到主要內容

參考動作

參考動作決定當您的應用程式刪除或更新相關記錄時,記錄會發生什麼情況。

從 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,則對應的標籤指派也會在 TagOnPosts 中刪除,使用 Cascade 參考動作
  • 如果您刪除 User,則會從所有貼文中移除作者,方法是將欄位值設定為 Null,因為使用了 SetNull 參考動作。為了允許這樣做,UseruserId 必須是 Post 中的選用欄位。

Prisma ORM 支援以下參考動作

參考動作預設值

如果您未指定參考動作,Prisma ORM 會使用以下預設值

子句選用關聯強制關聯
onDeleteSetNullRestrict
onUpdateCascadeCascade

例如,在以下結構描述中,所有 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 的預設參考動作 RestrictonUpdateCascade 會套用。

注意事項

以下注意事項適用

  • 參考動作支援隱含多對多關聯。若要使用參考動作,您必須定義明確的多對多關聯,並在聯結表格上定義您的參考動作。
  • 參考動作與必要/選用關聯的某些組合不相容。例如,在必要關聯上使用 SetNull 會在刪除參考記錄時導致資料庫錯誤,因為非可為 Null 的約束會被違反。請參閱此 GitHub 問題以取得更多資訊。

參考動作的類型

下表顯示每個資料庫支援哪些參考動作。

資料庫CascadeRestrictNoActionSetNullSetDefault
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 支援的唯一資料庫,允許您定義參考非可為 Null 欄位的 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 命令)時,新的預設值將會套用到您的資料庫。

資訊

與您第一次執行內省不同,新的參考動作子句和屬性將不會由 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()