リレーションのトラブルシューティング
スキーマのモデリングは、予期しない結果をもたらすことがあります。このセクションでは、それらの最も顕著なものを取り上げることを目的としています。
リレーションフィールドの順序が変更されると、暗黙的な多対多自己リレーションが不正なデータを返す
問題
次の暗黙的な多対多自己リレーションでは、`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テーブル)を内部で処理させることを意味します。モデルごとに非スカラー型の配列/リストを定義するだけで済みます。暗黙的な多対多リレーションを参照してください。
問題が発生する可能性があるのは、明示的なm-nリレーションシップを作成する場合、つまり、リレーションテーブルを自分で作成および処理する場合です。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テーブル)には、プライマリキーがありません。
解決策
明示的なリレーション構文を使用し、結合モデルを手動で作成し、この結合モデルにプライマリキーがあることを確認する必要があります。