跳至主要内容

SQL Server 和 MongoDB 中引用操作的特殊规则

如果使用引用操作,某些数据库有一些需要考虑的特定要求。

  • 如果关系链导致循环或多条级联路径,则 Microsoft SQL Server 不允许在外键上执行级联引用操作。如果外键上的引用操作设置为除NO ACTION(或如果 Prisma ORM 管理引用完整性,则为NoAction)之外的其他操作,则服务器将在执行 SQL 时检查循环或多条级联路径,并在检测到时返回错误。

  • 对于 MongoDB,在 Prisma ORM 中使用引用操作要求对于任何具有自引用关系或三个模型之间循环的数据模型,必须将引用操作设置为NoAction,以防止引用操作模拟无限循环。请注意,默认情况下,MongoDB 使用relationMode = "prisma"模式,这意味着 Prisma ORM 管理引用完整性

给定 SQL

CREATE TABLE [dbo].[Employee] (
[id] INT NOT NULL IDENTITY(1,1),
[managerId] INT,
CONSTRAINT [PK__Employee__id] PRIMARY KEY ([id])
);

ALTER TABLE [dbo].[Employee]
ADD CONSTRAINT [FK__Employee__managerId]
FOREIGN KEY ([managerId]) REFERENCES [dbo].[Employee]([id])
ON DELETE CASCADE ON UPDATE CASCADE;

当运行 SQL 时,数据库将抛出以下错误

Introducing FOREIGN KEY constraint 'FK__Employee__managerId' on table 'Employee' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

在更复杂的数据模型中,查找级联路径可能会变得复杂。因此,在 Prisma ORM 中,数据模型在生成任何 SQL(在任何迁移期间运行)之前进行验证,突出显示属于路径的关系。这使得查找和打破这些操作链变得更加容易。

自关联(SQL Server 和 MongoDB)

以下模型描述了一个自关联,其中Employee可以有一个经理和管理人员,引用同一模型的条目。

model Employee {
id Int @id @default(autoincrement())
manager Employee? @relation(name: "management", fields: [managerId], references: [id])
managees Employee[] @relation(name: "management")
managerId Int?
}

这将导致以下错误

Error parsing attribute "@relation": A self-relation must have `onDelete` and `onUpdate` referential actions set to `NoAction` in one of the @relation attributes. (Implicit default `onDelete`: `SetNull`, and `onUpdate`: `Cascade`)

通过不定义任何操作,Prisma ORM 将使用以下默认值,具体取决于底层标量字段是否设置为可选或必填。

子句所有标量字段都是可选的至少一个标量字段是必填的
onDeleteSetNullNoAction
onUpdateCascadeCascade

由于上述关系中onUpdate的默认引用操作为Cascade,而onDelete的默认引用操作为SetNull,因此会产生循环,解决方法是显式将onUpdateonDelete值设置为NoAction

model Employee {
id Int @id @default(autoincrement())
manager Employee @relation(name: "management", fields: [managerId], references: [id])
manager Employee @relation(name: "management", fields: [managerId], references: [id], onDelete: NoAction, onUpdate: NoAction)
managees Employee[] @relation(name: "management")
managerId Int
}

三个表之间的循环关系(SQL Server 和 MongoDB)

以下模型描述了ChickenEggFox之间的循环关系,其中每个模型都引用其他模型。

model Chicken {
id Int @id @default(autoincrement())
egg Egg @relation(fields: [eggId], references: [id])
eggId Int
predators Fox[]
}

model Egg {
id Int @id @default(autoincrement())
predator Fox @relation(fields: [predatorId], references: [id])
predatorId Int
parents Chicken[]
}

model Fox {
id Int @id @default(autoincrement())
meal Chicken @relation(fields: [mealId], references: [id])
mealId Int
foodStore Egg[]
}

这将在循环中每个关系字段中导致三个验证错误。

第一个错误出现在Chicken模型中的egg关系中

Error parsing attribute "@relation": Reference causes a cycle. One of the @relation attributes in this cycle must have `onDelete` and `onUpdate` referential actions set to `NoAction`. Cycle path: Chicken.egg → Egg.predator → Fox.meal. (Implicit default `onUpdate`: `Cascade`)

第二个错误出现在Egg模型中的predator关系中

Error parsing attribute "@relation": Reference causes a cycle. One of the @relation attributes in this cycle must have `onDelete` and `onUpdate` referential actions set to `NoAction`. Cycle path: Egg.predator → Fox.meal → Chicken.egg. (Implicit default `onUpdate`: `Cascade`)

第三个错误出现在Fox模型中的meal关系中

Error parsing attribute "@relation": Reference causes a cycle. One of the @relation attributes in this cycle must have `onDelete` and `onUpdate` referential actions set to `NoAction`. Cycle path: Fox.meal → Chicken.egg → Egg.predator. (Implicit default `onUpdate`: `Cascade`)

由于关系字段是必填的,因此onDelete的默认引用操作为NoAction,但onUpdate的默认引用操作为Cascade,这会导致引用操作循环。解决方法是在任何一个关系中将onUpdate值设置为NoAction

model Chicken {
id Int @id @default(autoincrement())
egg Egg @relation(fields: [eggId], references: [id])
egg Egg @relation(fields: [eggId], references: [id], onUpdate: NoAction)
eggId Int
predators Fox[]
}

model Egg {
id Int @id @default(autoincrement())
predator Fox @relation(fields: [predatorId], references: [id])
predator Fox @relation(fields: [predatorId], references: [id], onUpdate: NoAction)
predatorId Int
parents Chicken[]
}

model Fox {
id Int @id @default(autoincrement())
meal Chicken @relation(fields: [mealId], references: [id])
meal Chicken @relation(fields: [mealId], references: [id], onUpdate: NoAction)
mealId Int
foodStore Egg[]
}

两个模型之间的多条级联路径(仅限 SQL Server)

数据模型描述了相同模型之间的两条不同路径,并且这两个关系都触发级联引用操作。

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

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

model Comment {
id Int @id @default(autoincrement())
writtenById Int
postId Int
writtenBy User @relation(fields: [writtenById], references: [id])
post Post @relation(fields: [postId], references: [id])
}

此数据模型的问题在于,从CommentUser有两条路径,以及这两个关系中onUpdate的默认操作都是Cascade。这会导致两个验证错误

第一个错误出现在writtenBy关系中

Error parsing attribute "@relation": When any of the records in model `User` is updated or deleted, the referential actions on the relations cascade to model `Comment` through multiple paths. Please break one of these paths by setting the `onUpdate` and `onDelete` to `NoAction`. (Implicit default `onUpdate`: `Cascade`)

第二个错误出现在post关系中

Error parsing attribute "@relation": When any of the records in model `User` is updated or deleted, the referential actions on the relations cascade to model `Comment` through multiple paths. Please break one of these paths by setting the `onUpdate` and `onDelete` to `NoAction`. (Implicit default `onUpdate`: `Cascade`)

错误表示,通过更新User模型中记录的主键,更新将通过writtenBy关系在CommentUser之间级联一次,并再次通过post关系从Post模型级联,因为PostComment模型相关联。

解决方法是将writtenBypost关系字段中的onUpdate引用操作设置为NoAction,或者通过更改author关系中的操作从Post模型进行设置

model Comment {
id Int @id @default(autoincrement())
writtenById Int
postId Int
writtenBy User @relation(fields: [writtenById], references: [id])
writtenBy User @relation(fields: [writtenById], references: [id], onUpdate: NoAction)
post Post @relation(fields: [postId], references: [id])
}

model Comment {
id Int @id @default(autoincrement())
writtenById Int
postId Int
writtenBy User @relation(fields: [writtenById], references: [id])
post Post @relation(fields: [postId], references: [id])
post Post @relation(fields: [postId], references: [id], onUpdate: NoAction)
}

model Post {
id Int @id @default(autoincrement())
authorId Int
author User @relation(fields: [authorId], references: [id])
author User @relation(fields: [authorId], references: [id], onUpdate: NoAction)
comments Comment[]
}