关系故障排除
建模您的 Schema 有时可能会产生一些意外结果。本节旨在涵盖其中最突出的一些问题。
如果关系字段的顺序发生变化,隐式多对多自引用关系会返回不正确的数据
问题
在以下隐式多对多自引用关系中,a_eats
(1) 和 b_eatenBy
(2) 中关系字段的字典序
model Animal {
id Int @id @default(autoincrement())
name String
a_eats Animal[] @relation(name: "FoodChain")
b_eatenBy Animal[] @relation(name: "FoodChain")
}
SQL 中生成的关系表如下所示,其中 A
代表猎物 (a_eats
),B
代表捕食者 (b_eatenBy
)
A | B |
---|---|
8 (浮游生物) | 7 (三文鱼) |
7 (三文鱼) | 9 (熊) |
以下查询返回三文鱼的猎物和捕食者
const getAnimals = await prisma.animal.findMany({
where: {
name: 'Salmon',
},
include: {
b_eats: true,
a_eatenBy: true,
},
})
{
"id": 7,
"name": "Salmon",
"b_eats": [
{
"id": 8,
"name": "Plankton"
}
],
"a_eatenBy": [
{
"id": 9,
"name": "Bear"
}
]
}
现在更改关系字段的顺序
model Animal {
id Int @id @default(autoincrement())
name String
b_eats Animal[] @relation(name: "FoodChain")
a_eatenBy Animal[] @relation(name: "FoodChain")
}
迁移您的更改并重新生成 Prisma Client。当您使用更新后的字段名称运行相同的查询时,Prisma Client 返回不正确的数据(三文鱼现在吃熊,被浮游生物吃掉)
const getAnimals = await prisma.animal.findMany({
where: {
name: 'Salmon',
},
include: {
b_eats: true,
a_eatenBy: true,
},
})
{
"id": 1,
"name": "Salmon",
"b_eats": [
{
"id": 3,
"name": "Bear"
}
],
"a_eatenBy": [
{
"id": 2,
"name": "Plankton"
}
]
}
尽管 Prisma Schema 中关系字段的字典序发生了变化,但数据库中的列 A
和 B
没有改变(它们未被重命名,数据也未移动)。因此,A
现在代表捕食者 (a_eatenBy
),B
代表猎物 (b_eats
)
A | B |
---|---|
8 (浮游生物) | 7 (三文鱼) |
7 (三文鱼) | 9 (熊) |
解决方案
如果您在隐式多对多自引用关系中重命名关系字段,请确保保持字段的字母顺序——例如,通过添加 a_
和 b_
前缀。
如何使用多对多关系中的关系表
定义多对多(m-n)关系有几种方式,隐式或显式。隐式意味着让 Prisma ORM 在底层处理关系表(JOIN 表),您只需在每个模型上为非标量类型定义一个数组/列表,请参阅隐式多对多关系。
您可能会遇到麻烦的地方是创建显式多对多(m-n)关系时,即手动创建和处理关系表。可能容易忽略的是,Prisma ORM 要求关系的双方都存在。
以下面的例子为例,这里创建了一个关系表作为 Post
和 Category
表之间的 JOIN。然而,这行不通,因为关系表(PostCategories
)必须分别与另外两个模型形成一对多关系。
Post
到 PostCategories
以及 Category
到 PostCategories
模型中缺少反向关系字段。
// This example schema shows how NOT to define an explicit m-n relation
model Post {
id Int @id @default(autoincrement())
title String
categories Category[] // This should refer to PostCategories
}
model PostCategories {
post Post @relation(fields: [postId], references: [id])
postId Int
category Category @relation(fields: [categoryId], references: [id])
categoryId Int
@@id([postId, categoryId])
}
model Category {
id Int @id @default(autoincrement())
name String
posts Post[] // This should refer to PostCategories
}
要解决此问题,Post
模型需要定义一个与关系表 PostCategories
的多重关系字段。Category
模型也适用同样的情况。
这是因为关系模型与它连接的其他两个模型形成一对多关系。
model Post {
id Int @id @default(autoincrement())
title String
categories Category[]
postCategories PostCategories[]
}
model PostCategories {
post Post @relation(fields: [postId], references: [id])
postId Int
category Category @relation(fields: [categoryId], references: [id])
categoryId Int
@@id([postId, categoryId])
}
model Category {
id Int @id @default(autoincrement())
name String
posts Post[]
postCategories PostCategories[]
}
在多对多关系中使用 @relation
属性
在构建隐式多对多关系时,在模型上的关系字段中添加 @relation("Post")
注解似乎是合乎逻辑的。
model Post {
id Int @id @default(autoincrement())
title String
categories Category[] @relation("Category")
Category Category? @relation("Post", fields: [categoryId], references: [id])
categoryId Int?
}
model Category {
id Int @id @default(autoincrement())
name String
posts Post[] @relation("Post")
Post Post? @relation("Category", fields: [postId], references: [id])
postId Int?
}
然而,这会告诉 Prisma ORM 预期存在**两个**独立的一对多关系。有关使用 @relation
属性的更多信息,请参阅消歧关系。
以下示例是定义隐式多对多关系的正确方法。
model Post {
id Int @id @default(autoincrement())
title String
categories Category[] @relation("Category")
categories Category[]
}
model Category {
id Int @id @default(autoincrement())
name String
posts Post[] @relation("Post")
posts Post[]
}
@relation
注解还可用于命名隐式多对多关系中创建的底层关系表。
model Post {
id Int @id @default(autoincrement())
title String
categories Category[] @relation("CategoryPostRelation")
}
model Category {
id Int @id @default(autoincrement())
name String
posts Post[] @relation("CategoryPostRelation")
}
在强制主键的数据库中使用多对多(m-n)关系
问题
一些云提供商强制所有表都必须存在主键。然而,Prisma ORM 为使用隐式语法的多对多关系创建的任何关系表(JOIN 表)(通过 @relation
表示)都没有主键。
解决方案
您需要使用显式关系语法,手动创建联结模型,并验证此联结模型具有主键。