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 将使用以下默认值,具体取决于底层标量字段是否设置为可选或必填。
子句 | 所有标量字段都是可选的 | 至少一个标量字段是必填的 |
---|---|---|
onDelete | SetNull | NoAction |
onUpdate | Cascade | Cascade |
由于上述关系中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[]
}