关系
关系是 Prisma 架构中两个模型之间的连接。例如,User
和 Post
之间存在一对多关系,因为一个用户可以拥有多个博文。
以下 Prisma 架构定义了 User
和 Post
模型之间的一对多关系。定义关系中涉及的字段已突出显示
- 关系型数据库
- MongoDB
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)
}
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 ORM 层面,User
/ Post
关系由以下部分组成
- 两个 关系字段:
author
和posts
。关系字段定义了 Prisma ORM 层面上模型之间的连接,并且不存在于数据库中。这些字段用于生成 Prisma 客户端。 - 标量
authorId
字段,它由@relation
属性引用。此字段确实存在于数据库中 - 它是连接Post
和User
的外键。
在 Prisma ORM 层面,两个模型之间的连接始终由关系的每一侧上的 关系字段 表示。
数据库中的关系
关系型数据库
以下实体关系图定义了关系型数据库中 User
和 Post
表之间相同的一对多关系
在 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
文档。
@db.ObjectId
用于 ID 和关系标量字段
如果您的模型的 ID 是一个 ObjectId
(由一个 String
字段表示),您必须在模型的 ID 和关系另一侧的关系标量字段中添加 @db.ObjectId
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
],
},
},
})
在底层数据库中,此查询
- 创建一个
User
,并为其生成一个自动id
(例如,20
) - 创建两个新的
Post
记录,并将这两个记录的authorId
设置为20
检索记录并包含相关记录
以下查询按 id
检索 User
,并包含任何相关的 Post
记录
const getAuthor = await prisma.user.findUnique({
where: {
id: "20",
},
include: {
posts: true, // All posts where authorId == 20
},
});
在底层数据库中,此查询
- 检索
id
为20
的User
记录 - 检索
authorId
为20
的所有Post
记录
将现有记录关联到另一个现有记录
以下查询将现有 Post
记录与现有 User
记录关联起来
const updateAuthor = await prisma.user.update({
where: {
id: 20,
},
data: {
posts: {
connect: {
id: 4,
},
},
},
})
在底层数据库中,此查询使用 嵌套的 connect
查询 将 id
为 4 的帖子链接到 id
为 20 的用户。查询通过以下步骤完成此操作
- 查询首先查找
id
为20
的用户。 - 然后,查询将
authorID
外键设置为20
。这将id
为4
的帖子链接到id
为20
的用户。
在此查询中,authorID
的当前值无关紧要。查询将 authorID
更改为 20
,无论其当前值如何。
关系类型
Prisma ORM 中有三种不同的关系类型(或 基数)
以下 Prisma 架构包含每种关系类型
- 一对一:
User
↔Profile
- 一对多:
User
↔Post
- 多对多:
Post
↔Category
- 关系型数据库
- MongoDB
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[]
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
posts Post[]
profile Profile?
}
model Profile {
id String @id @default(auto()) @map("_id") @db.ObjectId
user User @relation(fields: [userId], references: [id])
userId String @unique @db.ObjectId // relation scalar field (used in the `@relation` attribute above)
}
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)
categories Category[] @relation(fields: [categoryIds], references: [id])
categoryIds String[] @db.ObjectId
}
model Category {
id String @id @default(auto()) @map("_id") @db.ObjectId
posts Post[] @relation(fields: [postIds], references: [id])
postIds String[] @db.ObjectId
}
请注意,关系型数据库和 MongoDB 之间的语法略有不同 - 尤其是对于 多对多关系。
对于关系型数据库,以下实体关系图表示对应于示例 Prisma 架构的数据库
对于 MongoDB,Prisma ORM 使用的是 规范化数据模型设计,这意味着文档以类似于关系型数据库的方式通过 ID 相互引用。有关更多详细信息,请参阅 MongoDB 部分。
隐式和显式多对多关系
关系型数据库中的多对多关系可以通过两种方式建模
隐式多对多关系要求两个模型都具有单个 @id
。请注意以下事项
- 您不能使用 多字段 ID
- 您不能使用
@unique
代替@id
要使用这些功能中的任何一个,您必须设置显式的多对多关系。
隐式多对多关系仍然在底层数据库中的关系表中体现。但是,Prisma ORM 管理这个关系表。
如果您使用隐式多对多关系而不是显式关系,它将使 Prisma Client API 更简单(例如,在 嵌套写入 中,您少了一个嵌套级别)。
如果您没有使用 Prisma Migrate,而是从 内省 获取数据模型,您仍然可以通过遵循 Prisma ORM 的 关系表约定 来使用隐式多对多关系。
关系字段
关系 字段 是 Prisma 模型 上的字段,不具有 标量类型。相反,它们的类型是另一个模型。
每个关系必须恰好有两个关系字段,每个模型上一个。在一对一和一对多关系的情况下,需要一个额外的关系标量字段,它由 @relation
属性中的两个关系字段之一链接。此关系标量字段是底层数据库中外键的直接表示。
- 关系型数据库
- MongoDB
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)
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
role Role @default(USER)
posts Post[] // relation field (defined only at the Prisma ORM level)
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
author User @relation(fields: [authorId], references: [id]) // relation field (uses the relation scalar field `authorId` below)
authorId String @db.ObjectId // relation scalar field (used in the `@relation` attribute above)
}
posts
和 author
都是关系字段,因为它们的类型不是标量类型,而是其他模型。
还要注意,带注释的关系字段 author
需要在 @relation
属性中链接 Post
模型上的关系标量字段 authorId
。关系标量字段表示底层数据库中的外键。
另一个名为 posts
的关系字段纯粹在 Prisma ORM 级别定义,它不会在数据库中体现。
带注释的关系字段
需要关系的一侧用 @relation
属性注释的关系被称为带注释的关系字段。这包括
- 一对一关系
- 一对多关系
- 仅限 MongoDB 的多对多关系
用 @relation
属性注释的关系的一侧表示在底层数据库中存储外键的一侧。“实际”代表外键的字段也需要在关系的那一侧,它被称为关系标量字段,并在 @relation
属性中引用。
- 关系型数据库
- MongoDB
author User @relation(fields: [authorId], references: [id])
authorId Int
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId
当标量字段在 @relation
属性的 fields
中使用时,它就成为一个关系标量字段。
关系标量字段
关系标量字段命名约定
因为关系标量字段总是属于关系字段,所以以下命名约定很常见
- 关系字段:
author
- 关系标量字段:
authorId
(关系字段名称 +Id
)
@relation
属性
@relation
属性只能应用于 关系字段,不能应用于 标量字段。
@relation
属性是必需的,当
- 您定义一对一或一对多关系时,它在关系的一侧(使用相应的关联标量字段)是必需的。
- 您需要区分关系(例如,当您在相同模型之间有两个关系时)。
- 您定义一个 自关系。
- 您定义 MongoDB 的多对多关系。
- 您需要控制关系表如何在底层数据库中表示(例如,使用特定名称作为关系表)。
注意:关系数据库中的 隐式多对多关系 不需要
@relation
属性。
区分关系
当您在相同两个模型之间定义两个关系时,您需要在 @relation
属性中添加 name
参数以区分它们。例如,考虑以下模型
- 关系型数据库
- MongoDB
// 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?
}
// NOTE: This schema is intentionally incorrect. See below for a working solution.
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String?
writtenPosts Post[]
pinnedPost Post?
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String?
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId
pinnedBy User? @relation(fields: [pinnedById], references: [id])
pinnedById String? @db.ObjectId
}
在这种情况下,关系是不明确的,有四种不同的解释方法
User.writtenPosts
↔Post.author
+Post.authorId
User.writtenPosts
↔Post.pinnedBy
+Post.pinnedById
User.pinnedPost
↔Post.author
+Post.authorId
User.pinnedPost
↔Post.pinnedBy
+Post.pinnedById
为了区分这些关系,您需要使用 @relation
属性对关系字段进行注释,并提供 name
参数。您可以设置任何 name
(除了空字符串 ""
),但它必须在关系的双方都相同。
- 关系型数据库
- MongoDB
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
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String?
writtenPosts Post[] @relation("WrittenPosts")
pinnedPost Post? @relation("PinnedPost")
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String?
author User @relation("WrittenPosts", fields: [authorId], references: [id])
authorId String @db.ObjectId
pinnedBy User? @relation("PinnedPost", fields: [pinnedById], references: [id])
pinnedById String? @unique @db.ObjectId
}