メインコンテンツにスキップ

リレーションのトラブルシューティング

スキーマのモデリングは、予期しない結果をもたらすことがあります。このセクションでは、それらの最も顕著なものを取り上げることを目的としています。

リレーションフィールドの順序が変更されると、暗黙的な多対多自己リレーションが不正なデータを返す

問題

次の暗黙的な多対多自己リレーションでは、`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`)を表します。

AB
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`)を表すようになりました。

AB
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テーブル)には、プライマリキーがありません。

解決策

明示的なリレーション構文を使用し、結合モデルを手動で作成し、この結合モデルにプライマリキーがあることを確認する必要があります。