跳至主要内容

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`,因此会创建一个循环,解决方法是明确将 `onUpdate` 和 `onDelete` 值设置为 `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)

以下模型描述了一个 `Chicken`、`Egg` 和 `Fox` 之间的循环关系,其中每个模型都引用了另一个模型。

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])
}

此数据模型中的问题是如何从 `Comment` 到 `User` 有两条路径,以及这两个关系中的默认 `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` 关系在 `Comment` 和 `User` 之间级联一次,并再次通过 `Post` 模型从 `post` 关系级联,因为 `Post` 与 `Comment` 模型相关。

解决方法是在 `writtenBy` 或 `post` 关系字段中将 `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[]
}