跳到主要內容

引用操作

引用操作決定了當您的應用程式刪除或更新相關記錄時,記錄會發生什麼。

從 2.26.0 版本開始,您可以在 Prisma schema 的關係欄位上定義引用操作。這允許您在 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 schema 中定義資料模型之間的關係時,您會使用關係欄位資料庫中不存在)和標量欄位資料庫中存在)。這些外部索引鍵在資料庫級別連線模型。

參照完整性規定,這些外部索引鍵必須引用相關資料庫表中現有的主鍵值。在您的 Prisma schema 中,這通常由相關模型上的 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,則由於 SetNull 引用操作,透過將欄位值設定為 Null,作者將從所有文章中刪除。為允許此操作,UseruserId 必須是 Post 中的可選欄位。

Prisma ORM 支援以下引用操作

引用操作預設值

如果您未指定引用操作,Prisma ORM 將使用以下預設值

子句可選關係強制關係
onDelete (刪除時)SetNull (設為 Null)Restrict (限制)
onUpdate (更新時)Cascade (級聯)Cascade (級聯)

例如,在以下 schema 中,所有 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[]
}

該 schema 未在強制性 author 關係欄位上明確定義引用操作,這意味著將應用 onDelete 的預設引用操作 RestrictonUpdate 的預設引用操作 Cascade

注意事項

適用以下注意事項

  • 隱式多對多關係不支援引用操作。要使用引用操作,您必須定義一個顯式多對多關係,並在連線表上定義您的引用操作。
  • 引用操作與必需/可選關係的某些組合不相容。例如,在必需關係上使用 SetNull 會在刪除引用記錄時導致資料庫錯誤,因為非空約束會被違反。有關更多資訊,請參閱此 GitHub 問題

引用操作型別

下表顯示了每種資料庫支援的引用操作。

資料庫Cascade (級聯)Restrict (限制)NoAction (無操作)SetNull (設為 Null)SetDefault (設為預設值)
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 schema 中的 SetDefault 引用操作。

PostgreSQL

PostgreSQL 是 Prisma ORM 支援的唯一允許您定義引用非空欄位的 SetNull 引用操作的資料庫。但是,當該操作在執行時觸發時,這會引發外部索引鍵約束錯誤。

因此,當您在(預設)foreignKeys 關係模式中將 postgres 設定為資料庫提供者時,Prisma ORM 會警告使用者將包含在具有 SetNull 引用操作的 @relation 屬性中的任何欄位標記為可選。對於所有其他資料庫提供者,Prisma ORM 將因驗證錯誤而拒絕 schema。

SQL Server

SQL Server 資料庫不提供Restrict,但您可以使用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 無法更改。

警告

Microsoft SQL Server提供 Restrict 操作,並且會觸發 schema 驗證錯誤。您可以改用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 及更早版本升級路徑

升級時有幾種路徑可供選擇,根據期望的結果會產生不同的結果。

如果您當前使用遷移工作流,可以執行內省來檢查預設值如何在您的 schema 中反映。如果需要,您可以手動更新資料庫。

您也可以選擇跳過檢查預設值並執行遷移以使用新預設值更新資料庫。

以下假設您已升級到 2.26.0 或更高版本並啟用了預覽功能標誌,或者已升級到 3.0.0 或更高版本

使用內省

如果您內省您的資料庫,資料庫級別配置的引用操作將反映在您的 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()deleteMany()方法,級聯刪除將現在被執行,因為 referentialActions 預覽功能移除了 Prisma Client 中先前阻止執行時級聯刪除的安全網。請務必檢查您的程式碼並進行相應調整。

請確保您對 schema 中所有 onDelete: Cascade 的情況都滿意。如果不是,則

  • 修改您的 Prisma schema 並 db pushdev migrate 以更改資料庫

  • 如果您使用僅內省工作流,則手動更新底層資料庫

以下示例將導致級聯刪除,如果 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 VSCode 擴充套件不會自動將新的引用操作子句和屬性新增到您的 prisma schema 中。如果您希望使用新預設值以外的任何其他內容,則必須手動新增它們。

在您的 Prisma schema 中顯式定義引用操作是可選的。如果您未為某個關係顯式定義引用操作,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 之間一對多關係的部落格 schema,並在 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()
© . This site is unofficial and not affiliated with Prisma Data, Inc.