跳到主要内容

关系

关系是 Prisma 模式中两个模型之间的连接。例如,UserPost 之间存在一对多关系,因为一个用户可以拥有多个博客帖子。

以下 Prisma 模式定义了 UserPost 模型之间的一对多关系。定义关系所涉及的字段会突出显示

model User {
id Int @id @default(autoincrement())
posts Post[]
}

model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id])
authorId Int // relation scalar field (used in the `@relation` attribute above)
}

在 Prisma ORM 级别,User / Post 关系由以下组成

  • 两个关系字段authorposts。关系字段定义了 Prisma ORM 级别模型之间的连接,并且不存在于数据库中。这些字段用于生成 Prisma 客户端。
  • 标量 authorId 字段,该字段由 @relation 属性引用。此字段确实存在于数据库中 - 它是连接 PostUser 的外键。

在 Prisma ORM 级别,两个模型之间的连接始终由关系每一侧上的关系字段表示。

数据库中的关系

关系数据库

以下实体关系图在关系数据库中定义了 UserPost 表之间的相同一对多关系

A one-to-many relationship between a user and posts table.

在 SQL 中,您可以使用外键在两个表之间创建关系。外键存储在关系的一侧。我们的示例由以下组成

  • Post 表中名为 authorId 的外键列。
  • User 表中名为 id 的主键列。Post 表中的 authorId 列引用 User 表中的 id 列。

在 Prisma 模式中,外键/主键关系由 author 字段上的 @relation 属性表示

author     User        @relation(fields: [authorId], references: [id])

注意:Prisma 模式中的关系表示数据库中表之间存在的关系。如果关系在数据库中不存在,则它在 Prisma 模式中也不存在。

MongoDB

对于 MongoDB,Prisma ORM 当前使用规范化数据模型设计,这意味着文档以类似于关系数据库的方式通过 ID 相互引用。

以下文档表示 User(在 User 集合中)

{ "_id": { "$oid": "60d5922d00581b8f0062e3a8" }, "name": "Ella" }

以下 Post 文档列表(在 Post 集合中)每个都有一个引用同一用户的 authorId 字段

[
{
"_id": { "$oid": "60d5922e00581b8f0062e3a9" },
"title": "How to make sushi",
"authorId": { "$oid": "60d5922d00581b8f0062e3a8" }
},
{
"_id": { "$oid": "60d5922e00581b8f0062e3aa" },
"title": "How to re-install Windows",
"authorId": { "$oid": "60d5922d00581b8f0062e3a8" }
}
]

此数据结构表示一对多关系,因为多个 Post 文档引用同一个 User 文档。

ID 和关系标量字段上的 @db.ObjectId

如果模型的 ID 是 ObjectId(由 String 字段表示),则必须将 @db.ObjectId 添加到模型的 ID 关系另一侧的关系标量字段

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
posts Post[]
}

model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId // relation scalar field (used in the `@relation` attribute above)
}

Prisma 客户端中的关系

Prisma 客户端是从 Prisma 模式生成的。以下示例演示了在使用 Prisma 客户端获取、创建和更新记录时关系如何体现。

创建记录和嵌套记录

以下查询创建一个 User 记录和两个连接的 Post 记录

const userAndPosts = await prisma.user.create({
data: {
posts: {
create: [
{ title: 'Prisma Day 2020' }, // Populates authorId with user's id
{ title: 'How to write a Prisma schema' }, // Populates authorId with user's id
],
},
},
})

在底层数据库中,此查询

  1. 创建一个带有自动生成的 idUser(例如,20
  2. 创建两个新的 Post 记录并将两个记录的 authorId 设置为 20

以下查询按 id 检索 User 并包括任何相关的 Post 记录

const getAuthor = await prisma.user.findUnique({
where: {
id: "20",
},
include: {
posts: true, // All posts where authorId == 20
},
});

在底层数据库中,此查询

  1. 检索 id20User 记录
  2. 检索所有 authorId20Post 记录

将现有记录与另一个现有记录关联

以下查询将现有 Post 记录与现有 User 记录关联

const updateAuthor = await prisma.user.update({
where: {
id: 20,
},
data: {
posts: {
connect: {
id: 4,
},
},
},
})

在底层数据库中,此查询使用嵌套的 connect 查询将 id 为 4 的帖子链接到 id 为 20 的用户。查询通过以下步骤执行此操作

  • 查询首先查找 id20 的用户。
  • 然后查询将 authorID 外键设置为 20。这会将 id 为 4 的帖子链接到 id 为 20 的用户。

在此查询中,authorID 的当前值无关紧要。无论其当前值如何,查询都会将 authorID 更改为 20

关系类型

Prisma ORM 中有三种不同类型的(或 基数)关系

以下 Prisma 模式包括每种类型的关系

  • 一对一:UserProfile
  • 一对多:UserPost
  • 多对多:PostCategory
model User {
id Int @id @default(autoincrement())
posts Post[]
profile Profile?
}

model Profile {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int @unique // relation scalar field (used in the `@relation` attribute above)
}

model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id])
authorId Int // relation scalar field (used in the `@relation` attribute above)
categories Category[]
}

model Category {
id Int @id @default(autoincrement())
posts Post[]
}
信息

此模式与示例数据模型相同,但删除了所有标量字段(除了必需的关系标量字段),以便您可以专注于关系字段

信息

此示例使用隐式多对多关系。这些关系不需要 @relation 属性,除非您需要消除关系的歧义

请注意,关系数据库和 MongoDB 之间的语法略有不同 - 特别是对于多对多关系

对于关系数据库,以下实体关系图表示与示例 Prisma 模式对应的数据库

The sample schema as an entity relationship diagram

对于 MongoDB,Prisma ORM 使用规范化数据模型设计,这意味着文档以类似于关系数据库的方式通过 ID 相互引用。有关更多详细信息,请参阅MongoDB 部分

隐式和显式多对多关系

关系数据库中的多对多关系可以用两种方式建模:

隐式多对多关系要求两个模型都只有一个 @id。请注意以下事项:

  • 您不能使用多字段 ID
  • 您不能使用 @unique 代替 @id

要使用这些功能中的任何一个,您必须设置显式多对多关系。

隐式多对多关系仍然在底层数据库中的关系表中体现。但是,Prisma ORM 会管理此关系表。

如果您使用隐式多对多关系而不是显式关系,它会使 Prisma Client API 更简单(例如,因为您在 嵌套写入 中减少了一层嵌套)。

如果您不使用 Prisma Migrate,而是从 内省 中获取数据模型,您仍然可以通过遵循 Prisma ORM 的 关系表的约定来利用隐式多对多关系。

关系字段

关系字段是 Prisma 模型具有标量类型的字段。相反,它们的类型是另一个模型。

每个关系必须恰好有两个关系字段,每个模型上一个。在 一对一 和 一对多 关系的情况下,还需要一个额外的*关系标量字段*,该字段通过 @relation 属性中的两个关系字段之一链接。此关系标量字段是底层数据库中*外键*的直接表示。

model User {
id Int @id @default(autoincrement())
email String @unique
role Role @default(USER)
posts Post[] // relation field (defined only at the Prisma ORM level)
}

model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id]) // relation field (uses the relation scalar field `authorId` below)
authorId Int // relation scalar field (used in the `@relation` attribute above)
}

postsauthor 都是关系字段,因为它们的类型不是标量类型,而是其他模型。

另请注意,带注释的关系字段 author 需要将 Post 模型上的关系标量字段 authorId 链接到 @relation 属性中。关系标量字段表示底层数据库中的外键。

另一个名为 posts 的关系字段纯粹在 Prisma ORM 级别定义,它不会在数据库中体现。

带注释的关系字段

需要使用 @relation 属性注释关系一侧的关系称为*带注释的关系字段*。 这包括:

  • 一对一关系
  • 一对多关系
  • 仅适用于 MongoDB 的多对多关系

使用 @relation 属性注释的关系一侧表示在底层数据库中存储外键的一侧。表示外键的“实际”字段也需要位于关系的该侧,它被称为*关系标量字段*,并在 @relation 属性中引用。

author     User    @relation(fields: [authorId], references: [id])
authorId Int

当标量字段在 @relation 属性的 fields 中使用时,它成为关系标量字段。

关系标量字段

关系标量字段命名约定

因为关系标量字段始终属于关系字段,所以以下命名约定很常见:

  • 关系字段:author
  • 关系标量字段:authorId (关系字段名称 + Id)

@relation 属性

@relation 属性只能应用于关系字段,而不能应用于标量字段

在以下情况下,需要 @relation 属性:

  • 当您定义一对一或一对多关系时,需要在关系的*一侧*上使用它(带有相应的关系标量字段)
  • 您需要消除关系的歧义(例如,当您在同一模型之间有两个关系时)
  • 您定义一个自引用关系
  • 您定义MongoDB 的多对多关系
  • 您需要控制关系表在底层数据库中的表示方式(例如,为关系表使用特定名称)

注意:关系数据库中的隐式多对多关系不需要 @relation 属性。

消除关系歧义

当您在相同的两个模型之间定义两个关系时,您需要在 @relation 属性中添加 name 参数来消除它们的歧义。 作为一个例子,说明为什么需要这样做,请考虑以下模型:

// NOTE: This schema is intentionally incorrect. See below for a working solution.

model User {
id Int @id @default(autoincrement())
name String?
writtenPosts Post[]
pinnedPost Post?
}

model Post {
id Int @id @default(autoincrement())
title String?
author User @relation(fields: [authorId], references: [id])
authorId Int
pinnedBy User? @relation(fields: [pinnedById], references: [id])
pinnedById Int?
}

在这种情况下,关系是模糊的,有四种不同的解释方式:

  • User.writtenPostsPost.author + Post.authorId
  • User.writtenPostsPost.pinnedBy + Post.pinnedById
  • User.pinnedPostPost.author + Post.authorId
  • User.pinnedPostPost.pinnedBy + Post.pinnedById

要消除这些关系的歧义,您需要使用 @relation 属性注释关系字段,并提供 name 参数。您可以设置任何 name (空字符串 "" 除外),但它在关系的两侧必须相同。

model User {
id Int @id @default(autoincrement())
name String?
writtenPosts Post[] @relation("WrittenPosts")
pinnedPost Post? @relation("PinnedPost")
}

model Post {
id Int @id @default(autoincrement())
title String?
author User @relation("WrittenPosts", fields: [authorId], references: [id])
authorId Int
pinnedBy User? @relation("PinnedPost", fields: [pinnedById], references: [id])
pinnedById Int? @unique
}