引用操作
引用操作決定了當您的應用程式刪除或更新相關記錄時,記錄會發生什麼。
從 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 記錄。
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 將使用預設值。
什麼是引用操作?
引用操作是定義當您執行update或delete查詢時,資料庫如何處理被引用記錄的策略。
資料庫級別的引用操作
如何使用引用操作
引用操作在@relation屬性中定義,並對映到底層資料庫中外部索引鍵約束上的操作。如果您未指定引用操作,Prisma ORM 會回退到預設值。
以下模型定義了 User 和 Post 之間的一對多關係,以及 Post 和 Tag 之間的多對多關係,並明確定義了引用操作
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,作者將從所有文章中刪除。為允許此操作,User和userId必須是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 的預設引用操作 Restrict 和 onUpdate 的預設引用操作 Cascade。
注意事項
適用以下注意事項
- 隱式多對多關係不支援引用操作。要使用引用操作,您必須定義一個顯式多對多關係,並在連線表上定義您的引用操作。
- 引用操作與必需/可選關係的某些組合不相容。例如,在必需關係上使用
SetNull會在刪除引用記錄時導致資料庫錯誤,因為非空約束會被違反。有關更多資訊,請參閱此 GitHub 問題。
引用操作型別
下表顯示了每種資料庫支援的引用操作。
| 資料庫 | Cascade (級聯) | Restrict (限制) | NoAction (無操作) | SetNull (設為 Null) | SetDefault (設為預設值) |
|---|---|---|---|---|---|
| PostgreSQL | ✔️ | ✔️ | ✔️ | ✔️⌘ | ✔️ |
| MySQL/MariaDB | ✔️ | ✔️ | ✔️ | ✔️ | ❌ (✔️†) |
| SQLite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| SQL Server | ✔️ | ❌‡ | ✔️ | ✔️ | ✔️ |
| CockroachDB | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| MongoDB†† | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
- † 請參閱MySQL 的特殊情況。
- ⌘ 請參閱PostgreSQL 的特殊情況。
- ‡ 請參閱SQL Server 的特殊情況。
- †† MongoDB 的引用操作在 Prisma ORM 3.7.0 及更高版本中可用。
引用操作的特殊情況
引用操作是 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如果依賴記錄的被引用標量欄位更新,則更新關係標量欄位。
用法示例
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阻止更改引用記錄的識別符號。
用法示例
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 無法刪除。User 的 id 無法更改。
Microsoft SQL Server上不提供 Restrict 操作,並且會觸發 schema 驗證錯誤。您可以改用NoAction,它會產生相同的結果並與 SQL Server 相容。
NoAction
NoAction 操作與 Restrict 類似,兩者之間的區別取決於所使用的資料庫
- PostgreSQL:
NoAction允許將檢查(表上是否存在引用行)推遲到事務後期。有關更多資訊,請參閱PostgreSQL 文件。 - MySQL:
NoAction的行為與Restrict完全相同。有關更多資訊,請參閱MySQL 文件。 - SQLite:當相關主鍵被修改或刪除時,不採取任何操作。有關更多資訊,請參閱SQLite 文件。
- SQL Server:當被引用的記錄被刪除或修改時,會引發錯誤。有關更多資訊,請參閱SQL Server 文件。
- MongoDB(從 3.6.0 版本開始預覽):當記錄被修改或刪除時,不會對任何相關記錄執行任何操作。
如果您在 Prisma Client 中管理關係而不是在資料庫中使用外部索引鍵,您應該注意,當前 Prisma ORM 只實現了引用操作。外部索引鍵還會建立約束,使得無法以違反這些約束的方式操作資料:資料庫會響應錯誤,而不是執行查詢。如果您在 Prisma Client 中模擬參照完整性,則不會建立這些約束,因此如果您將引用操作設定為 NoAction,將沒有任何檢查來阻止您破壞參照完整性。
用法示例
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 無法刪除。User 的 id 無法更改。
SetNull
-
onDelete: SetNull引用物件的標量欄位將被設定為NULL。 -
onUpdate: SetNull更新引用物件的識別符號時,引用物件的標量欄位將被設定為NULL。
SetNull 只適用於可選關係。在必需關係上,由於標量欄位不能為 null,因此會丟擲執行時錯誤。
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。
更改 User 的 id 時,其所有已發表文章的 authorId 將設定為 NULL。
SetDefault
-
onDelete: SetDefault引用物件的標量欄位將被設定為欄位的預設值。 -
onUpdate: SetDefault引用物件的標量欄位將被設定為欄位的預設值。
這些操作需要使用@default為關係標量欄位設定預設值。如果未為任何標量欄位提供預設值,則會丟擲執行時錯誤。
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”。
當 User 的 username 更改時,其現有文章的 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 DELETE 和 ON UPDATE 與預設值不匹配,它們將被顯式地設定在 schema 檔案中。
內省後,您可以檢視 schema 中的非預設子句。最重要的是檢查 onDelete 子句,在 2.25.0 及更早版本中,它的預設值為 Cascade。
如果您正在使用delete()或deleteMany()方法,級聯刪除將現在被執行,因為 referentialActions 預覽功能移除了 Prisma Client 中先前阻止執行時級聯刪除的安全網。請務必檢查您的程式碼並進行相應調整。
請確保您對 schema 中所有 onDelete: Cascade 的情況都滿意。如果不是,則
- 修改您的 Prisma schema 並
db push或dev 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 不再執行執行時檢查。您可以指定自定義引用操作來保留關係之間的參照完整性。
當您使用NoAction或Restrict來阻止記錄刪除時,2.26.0 之後的錯誤訊息將與 2.26.0 之前的錯誤訊息不同。這是因為它們現在由資料庫觸發,而不是Prisma Client。可以預期的新的錯誤程式碼是 P2003。
為確保捕獲這些新錯誤,您可以相應地調整程式碼。
捕獲錯誤示例
以下示例使用下面顯示具有 Post 和 User 之間一對多關係的部落格 schema,並在 author 欄位上設定Restrict引用操作。
這意味著,如果使用者有文章,則該使用者(及其文章)不能被刪除。
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()