跳至主要内容

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

快速摘要

此页面解释了在 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 自引用是否需要 NoAction?
  • 如何处理多级级联路径?

自引用(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,或者通过更改 Post 模型中 author 关系的操作来实现

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[]
}
© . This site is unofficial and not affiliated with Prisma Data, Inc.