リレーションのトラブルシューティング
スキーマのモデリングは、時に予期せぬ結果をもたらすことがあります。このセクションでは、その中で最も顕著なものをカバーすることを目的としています。
リレーションフィールドの順序が変更されると、暗黙的な多対多の自己リレーションが不正なデータを返す
問題
次の暗黙的な多対多の自己リレーションにおいて、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スキーマ内のリレーションフィールドの辞書順が変更されたにもかかわらず、データベース内のカラムA
とB
は変更されませんでした(名前が変更されたり、データが移動したりしていません)。したがって、A
は捕食者(a_eatenBy
)を表し、B
は被食者(b_eats
)を表します。
A | B |
---|---|
8 (プランクトン) | 7 (サケ) |
7 (サケ) | 9 (クマ) |
解決策
暗黙的な多対多の自己リレーションでリレーションフィールドの名前を変更する場合は、フィールドのアルファベット順を維持するようにしてください。たとえば、`a_`と`b_`をプレフィックスとして使用します。
多対多リレーションでリレーションテーブルを使用する方法
多対多(m-n)リレーションを定義するには、暗黙的または明示的な方法がいくつかあります。暗黙的とは、Prisma ORMにリレーションテーブル(JOINテーブル)を内部で処理させることを意味し、各モデルの非スカラー型に対して配列/リストを定義するだけで済みます。詳細については、「暗黙的な多対多リレーション」を参照してください。
問題が発生する可能性があるのは、「明示的な多対多リレーションシップ」を作成する場合です。つまり、リレーションテーブルを自分で作成および処理する場合です。Prisma ORMがリレーションの両側の存在を要求することを見落とされがちです。
次の例を見てください。ここでは、`Post`テーブルと`Category`テーブル間のJOINとして機能するリレーションテーブルが作成されています。しかし、リレーションテーブル(`PostCategories`)がそれぞれ他の2つのモデルと1対多のリレーションシップを形成する必要があるため、これは機能しません。
`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`モデルにも同様に適用されます。
これは、リレーションモデルが、結合している他の2つのモデルと1対多のリレーションシップを形成するためです。
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に2つの独立した1対多リレーションを期待するよう指示します。@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が(@relation
を介して表現される)暗黙的な構文を使用して多対多リレーションのために作成するリレーションテーブル(JOINテーブル)には主キーがありません。
解決策
明示的なリレーション構文を使用し、結合モデルを手動で作成し、その結合モデルに主キーがあることを確認する必要があります。