跳至主要内容

从 Sequelize 迁移

本指南介绍如何从 Sequelize 迁移到 Prisma ORM。它使用 Sequelize Express 示例 的扩展版本作为 示例项目 来演示迁移步骤。您可以在 GitHub 上找到此指南中使用的示例。

此迁移指南使用 PostgreSQL 作为示例数据库,但它同样适用于任何其他 Prisma 支持的 关系型数据库。

您可以在 Prisma ORM 与 Sequelize 页面上了解 Prisma ORM 如何与 Sequelize 进行比较。

迁移过程概述

请注意,无论您构建的是哪种应用程序或 API 层,从 Sequelize 迁移到 Prisma ORM 的步骤始终相同

  1. 安装 Prisma CLI
  2. 内省您的数据库
  3. 创建基线迁移
  4. 安装 Prisma Client
  5. 逐渐用 Prisma Client 替换您的 Sequelize 查询

无论您是在构建 REST API(例如,使用 Express、koa 或 NestJS)、GraphQL API(例如,使用 Apollo Server、TypeGraphQL 或 Nexus)还是任何其他使用 Sequelize 进行数据库访问的应用程序,这些步骤都适用。

Prisma ORM 非常适合 **增量采用**。这意味着,您不必一次性将整个项目从 Sequelize 迁移到 Prisma ORM,而是可以 *逐步* 将您的数据库查询从 Sequelize 迁移到 Prisma ORM。

示例项目的概述

在本指南中,我们将使用一个用 Express 构建的 REST API 作为 示例项目 迁移到 Prisma ORM。它有四个模型/实体

module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
})

User.associate = (models) => {
User.hasMany(models.Post, {
foreignKey: 'authorId',
as: 'posts',
})
User.hasOne(models.Profile, {
onDelete: 'CASCADE',
foreignKey: 'userId',
})
}
return User
}

这些模型具有以下关系

  • 1-1:UserProfile
  • 1-n:UserPost
  • m-n:PostCategory

相应的表是使用生成的 Sequelize 迁移创建的。

在本指南中,路由处理程序位于 src/controllers 目录中。模型位于 src/models 目录中。从那里,它们被拉入一个中心 src/routes.js 文件,该文件用于在 src/index.js 中设置所需的路由。

└── blog-sequelize
├── package.json
└──src
   ├── controllers
   │   ├── post.js
   │   └── user.js
   ├── models
   │   ├── Category.js
   │   ├── Post.js
   │   ├── Profile.js
   │   └── User.js
   ├── index.js
   └── routes.js

步骤 1. 安装 Prisma CLI

采用 Prisma ORM 的第一步是在您的项目中 安装 Prisma CLI

npm install prisma --save-dev

步骤 2. 内省您的数据库

2.1. 设置 Prisma ORM

在您内省数据库之前,您需要设置您的 Prisma 模式 并将 Prisma ORM 连接到您的数据库。在您的终端中运行以下命令以创建基本的 Prisma 模式文件:

npx prisma init

此命令为您创建了一个名为 prisma 的新目录,其中包含以下文件:

  • schema.prisma:您的 Prisma 模式,指定您的数据库连接和模型
  • .env:一个 dotenv 用于将您的数据库连接 URL 配置为环境变量

Prisma 模式当前如下所示:

prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

generator client {
provider = "prisma-client-js"
}
提示

如果您使用的是 VS Code,请确保安装了 Prisma VS Code 扩展 以获得语法高亮显示、格式化、自动完成以及更多酷炫的功能。

2.2. 连接您的数据库

如果您没有使用 PostgreSQL,则需要将 datasource 块上的 provider 字段调整为您当前使用的数据库。

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

完成后,您可以在 .env 文件中配置您的 数据库连接 URL。以下是 Sequelize 的数据库连接如何映射到 Prisma ORM 使用的连接 URL 格式:


假设您在 src/models/index.js 中具有以下数据库连接详细信息:


src/models/index.js
const sequelize = new Sequelize('blog-sequelize', 'alice', 'myPassword42', {
host: 'localhost',
dialect: 'postgres',
})

相应的连接 URL 在 Prisma ORM 中如下所示:


.env
DATABASE_URL="postgresql://alice:myPassword42@localhost:5432/blog-sequelize"

请注意,您可以选择通过将 schema 参数附加到连接 URL 来配置 PostgreSQL 模式


.env
DATABASE_URL="postgresql://alice:myPassword42@localhost:5432/blog-sequelize?schema=myschema"

如果未提供,则使用名为 public 的默认模式。


2.3. 使用 Prisma ORM 内省您的数据库

连接 URL 就位后,您可以 内省 您的数据库以生成 Prisma 模型。

npx prisma db pull

这将创建以下 Prisma 模型:

prisma/schema.prisma
model Categories {
id Int @id @default(autoincrement())
name String
createdAt DateTime
updatedAt DateTime
PostCategories PostCategories[]
}

model PostCategories {
createdAt DateTime
updatedAt DateTime
CategoryId Int
PostId Int
Categories Categories @relation(fields: [CategoryId], references: [id])
Posts Posts @relation(fields: [PostId], references: [id])

@@id([CategoryId, PostId])
}

model Posts {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
createdAt DateTime
updatedAt DateTime
authorId Int?
Users Users? @relation(fields: [authorId], references: [id])
PostCategories PostCategories[]
}

model Profiles {
id Int @id @default(autoincrement())
bio String
createdAt DateTime
updatedAt DateTime
userId Int? @unique
Users Users? @relation(fields: [userId], references: [id])
}

model SequelizeMeta {
name String @id
}

model Users {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime
updatedAt DateTime
Posts Posts[]
Profiles Profiles?
}

2.4. 创建基线迁移

要继续使用 Prisma Migrate 演变您的数据库模式,您需要 为您的数据库创建基线

首先,创建一个 migrations 目录,并在其中添加一个具有您首选迁移名称的目录。在本例中,我们将使用 0_init 作为迁移名称。

mkdir -p prisma/migrations/0_init

接下来,使用 prisma migrate diff 生成迁移文件。使用以下参数:

  • --from-empty:假设您正在从中迁移的数据模型为空
  • --to-schema-datamodel:使用 datasource 块中的 URL 获取当前数据库状态
  • --script:输出 SQL 脚本
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > prisma/migrations/0_init/migration.sql

查看生成的迁移以确保一切正确。

接下来,使用 prisma migrate resolve--applied 参数将迁移标记为已应用。

npx prisma migrate resolve --applied 0_init

该命令将通过将其添加到 _prisma_migrations 表中来将 0_init 标记为已应用。

您现在有了当前数据库模式的基线。要对数据库模式进行进一步更改,您可以更新 Prisma 模式并使用 prisma migrate dev 将更改应用到数据库。

2.5. 调整 createdAtupdatedAt 字段

生成的 Prisma 模型代表您的数据库表,并且是您程序化 Prisma Client API 的基础,它允许您向数据库发送查询。您将在我们的模型中调整createdAtupdatedAt字段。Sequelize 在数据库中创建表时不会向createdAt添加DEFAULT约束。因此,您将分别向createdAtupdatedAt列添加@default(now())@updatedAt属性。要详细了解 Prisma ORM 如何执行此操作,您可以阅读更多有关@default(now())@updatedAt的信息。

prisma/schema.prisma
model Categories {
id Int @id @default(autoincrement())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
PostCategories PostCategories[]
}

model PostCategories {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
CategoryId Int
PostId Int
Categories Categories @relation(fields: [CategoryId], references: [id])
Posts Posts @relation(fields: [PostId], references: [id])

@@id([CategoryId, PostId])
}

model Posts {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authorId Int?
Users Users? @relation(fields: [authorId], references: [id])
PostCategories PostCategories[]
}

model Profiles {
id Int @id @default(autoincrement())
bio String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int? @unique
Users Users? @relation(fields: [userId], references: [id])
}

model SequelizeMeta {
name String @id
}

model Users {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Posts Posts[]
Profiles Profiles?
}

2.6. 调整 Prisma 模式(可选)

通过内省生成的模型目前完全映射到您的数据库表。在本节中,您将学习如何调整 Prisma 模型的命名以符合Prisma ORM 的命名约定

所有这些调整都是完全可选的,如果您现在不想进行任何调整,可以随时跳到下一步。您可以在以后的任何时间返回并进行调整。

与 Prisma 模型当前的 snake_case 表示法相反,Prisma ORM 的命名约定是

  • 模型名称使用 PascalCase
  • 字段名称使用 camelCase

您可以通过使用@@map@map将 Prisma 模型和字段名称映射到底层数据库中现有的表和列名称来调整命名。

另请注意,您可以重命名关系字段以优化您稍后将用于向数据库发送查询的 Prisma Client API。例如,尽管我们将Posts模型名称单数化为Post,但user模型上的posts字段是一个列表,因此将其命名为posts是有意义的,以指示它是复数。

Sequelize 生成一个SequelizeMeta模型,该模型由库在内部使用,但不需要。因此,您将从模式中手动删除它。

这是一个解决这些问题的 Prisma 模式的调整版本

prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Category {
id Int @id @default(autoincrement())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
postCategories PostToCategories[]

@@map("Categories")
}

model PostToCategories {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
categoryId Int
postId Int
category Category @relation(fields: [categoryId], references: [id])
post Post @relation(fields: [postId], references: [id])

@@id([categoryId, postId])
@@map("PostCategories")
}

model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authorId Int?
author User? @relation(fields: [authorId], references: [id])
postToCategories PostToCategories[]

@@map("Posts")
}

model Profile {
id Int @id @default(autoincrement())
bio String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int? @unique
user User? @relation(fields: [userId], references: [id])

@@map("Profiles")
}

model User {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
profile Profile?

@@map("Users")
}

步骤 3. 安装 Prisma Client

下一步,您可以在项目中安装 Prisma Client,以便您可以开始替换当前使用 Sequelize 执行的项目中的数据库查询。

npm install @prisma/client

步骤 4. 用 Prisma Client 替换您的 Sequelize 查询

在本节中,我们将展示一些从 Sequelize 迁移到 Prisma Client 的示例查询,这些查询基于示例 REST API 项目中的示例路由。有关 Prisma Client API 与 Sequelize 的差异的全面概述,请查看API 对比页面。

首先,设置您将用于从各种路由处理程序发送数据库查询的PrismaClient实例。在src目录中创建一个名为prisma.js的新文件。

touch src/prisma.js

现在,实例化PrismaClient并将其从文件中导出,以便您以后可以在路由处理程序中使用它。

src/prisma.js
const { PrismaClient } = require('@prisma/client')

const prisma = new PrismaClient()

module.exports = prisma

控制器文件中的导入如下所示

src/controllers/post.js
const { Post, User, Category } = require('../models')
const { Op } = require('sequelize')
src/controllers/user.js
const { User } = require('../models')

从 Sequelize 迁移到 Prisma 时,您将更新控制器导入。

src/controllers/post.js
const prisma = require('../prisma')
src/controllers/user.js
const prisma = require('../prisma')

4.1. 替换GET请求中的查询

REST API 有四个接受GET请求的路由

  • /feed:返回所有已发布的帖子
  • /filterPosts?searchString=SEARCH_STRING:按SEARCH_STRING筛选返回的帖子
  • /post/:postId:返回特定帖子
  • /authors:返回作者列表

让我们深入研究实现这些请求的路由处理程序。

/feed

/feed处理程序当前实现如下

src/controllers/post.js
const feed = async (req, res) => {
try {
const feed = await Post.findAll({
where: { published: true },
include: ['author', 'categories'],
})
return res.json(feed)
} catch (error) {
return res.status(500).json(error)
}
}

请注意,每个返回的Post对象都包含与其关联的authorcategory的关系。使用 Sequelize,包含关系不是类型安全的。例如,如果检索到的关系中存在错别字,则您的数据库查询仅会在运行时失败——JavaScript 编译器在此处不提供任何安全性。

以下是使用 Prisma Client 实现相同路由的方式

src/controllers/post.js
const feed = async (req, res) => {
try {
const feed = await prisma.post.findMany({
where: { published: true },
include: { author: true, postToCategories: true },
})
return res.json(feed)
} catch (error) {
return res.status(500).json(error)
}
}

请注意,Prisma Client 包含author关系的方式是绝对类型安全的。如果您尝试包含Post模型上不存在的关系,则 JavaScript 编译器会抛出错误。

/filterPosts?searchString=SEARCH_STRING

/filterPosts处理程序当前实现如下

src/controllers/post.js
const filterPosts = async (req, res) => {
const { searchString } = req.query

try {
const filteredPosts = await Post.findAll({
where: {
[Op.or]: [
{
title: {
[Op.like]: `%${searchString}%`,
},
},
{
content: {
[Op.like]: `%${searchString}%`,
},
},
],
},
include: 'author',
})

res.json(filteredPosts)
} catch (error) {
return res.status(500).json(error)
}
}

使用 Prisma ORM,路由实现如下

src/controllers/post.js
const filterPosts = async (req, res) => {
const { searchString } = req.query

try {
const filteredPosts = prisma.post.findMany({
where: {
OR: [
{
title: { contains: searchString },
},
{
content: { contains: searchString },
},
],
},
})

res.json(filteredPosts)
} catch (error) {
return res.status(500).json(error)
}
}

请注意,Sequelize 提供了运算符符号 - Op - 用于查询数据。另一方面,Prisma ORM将多个where条件与隐式AND运算符结合起来,因此在这种情况下,Prisma Client 查询需要明确OR

/post/:postId

/post/:postId处理程序当前实现如下

src/controllers/post.js
const getPostById = async (req, res) => {
const { postId } = req.params

try {
const post = await Post.findOne({
where: { id: postId },
include: 'author',
})

return res.json(post)
} catch (error) {
return res.status(500).json(error)
}
}

使用 Prisma ORM,路由实现如下

src/controllers/post.js
const getPostById = async (req, res) => {
const { postId } = req.params

try {
const post = await prisma.post.findUnique({
where: { id: Number(postId) },
include: { author: true },
})

return res.json(post)
} catch (error) {
return res.status(500).json(error)
}
}

4.2. 替换POST请求中的查询

REST API 有三个接受POST请求的路由

  • /user:创建一个新的User记录
  • /post:创建一个新的User记录
  • /user/:userId/profile:为具有给定 ID 的User记录创建一个新的Profile记录

/user

/user处理程序当前实现如下

src/controllers/user.js
const createUser = async (req, res) => {
const { name, email } = req.body

try {
const user = await User.create({
name,
email,
})

return res.json(user)
} catch (error) {
return res.status(500).json(error)
}
}

使用 Prisma ORM,路由实现如下

src/controllers/user.js
const createUser = async (req, res) => {
const { name, email } = req.body

try {
const user = await prisma.user.create({
data: {
name,
email,
},
})

return res.json(user)
} catch (error) {
return res.status(500).json(error)
}
}

/post

/post处理程序当前实现如下

src/controllers/post.js
const createDraft = async (req, res) => {
const { title, content, authorEmail } = req.body

try {
const user = await User.findOne({ email: authorEmail })

const draft = await Post.create({
title,
content,
authorId: user.id,
})

res.json(draft)
} catch (error) {
return res.status(500).json(error)
}
}

使用 Prisma ORM,路由实现如下

src/controllers/post.js
const createDraft = async (req, res) => {
const { title, content, authorEmail } = req.body

try {
const draft = await prisma.post.create({
data: {
title,
content,
author: {
connect: { email: authorEmail },
},
},
})

res.json(draft)
} catch (error) {
return res.status(500).json(error)
}
}

请注意,Prisma Client 在此处的嵌套写入保存了初始查询,其中首先需要通过其email检索User记录。这是因为,使用 Prisma Client,您可以使用任何唯一属性连接关系中的记录。

/user/:userId/profile

/user/:userId/profile处理程序当前实现如下

src/controllers/user.js
const setUserBio = async (req, res) => {
const { userId } = req.params
const { bio } = req.body

try {
const user = await User.findOne({
where: {
id: Number(userId),
},
})

const updatedUser = await user.createProfile({ bio })

return res.json(updatedUser)
} catch (error) {
return res.status(500).json(error)
}
}

使用 Prisma ORM,路由实现如下

src/controllers/user.js
const setUserBio = async (req, res) => {
const { userId } = req.params
const { bio } = req.body

try {
const user = await prisma.user.update({
where: { id: Number(userId) },
data: {
profile: {
create: { bio },
},
},
})

return res.json(user)
} catch (error) {
return res.status(500).json(error)
}
}

4.3. 替换PUT请求中的查询

REST API 有一个接受PUT请求的路由

  • /addPostToCategory?postId=POST_ID&categoryId=CATEGORY_ID:将具有POST_ID的帖子添加到具有CATEGORY_ID的类别中

让我们深入研究实现这些请求的路由处理程序。

/addPostToCategory?postId=POST_ID&categoryId=CATEGORY_ID

/addPostToCategory?postId=POST_ID&categoryId=CATEGORY_ID处理程序当前实现如下

src/controllers/post.js
const addPostToCategory = async (req, res) => {
const { postId, categoryId } = req.query

try {
const post = await Post.findOne({
where: { id: postId },
})

const category = await Category.findOne({
where: { id: categoryId },
})

const updatedPost = await post.addCategory(category)

return res.json(updatedPost)
} catch (error) {
return res.status(500).json(error)
}
}

使用 Prisma ORM,路由实现如下

src/controllers/post.js
const addPostToCategory = async (req, res) => {
const { postId, categoryId } = req.query

try {
const post = await prisma.post.update({
data: {
postToCategories: {
create: {
categories: {
connect: { id: Number(categoryId) },
},
},
},
},
where: {
id: Number(postId),
},
})

return res.json(post)
} catch (error) {
return res.status(500).json(error)
}
}

请注意,可以通过将关系建模为隐式多对多关系来使此 Prisma Client 不那么冗长。在这种情况下,查询将如下所示

src/controllers/posts.js
const post = await prisma.post.update({
data: {
category: {
connect: { id: categoryId },
},
},
where: { id: postId },
})

更多

主键列

默认情况下,Sequelize 定义一个primaryKey并在未定义时使用自动生成的id。这是可选的。如果您想设置自己的主键,可以使用primaryKey: true并在您选择的字段中定义您首选的数据类型。

// changing the primary key column
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
postId: {
type: DataTypes.INTEGER,
primaryKey: true,
},
})
return Post
}

// changing the id DataType
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
id: {
type: DataTypes.UUID, // alternative: DataTypes.STRING
primaryKey: true,
},
})
return Post
}

表名推断

Sequelize 从模型名称推断表名。当未提供表名时,Sequelize 会自动将模型名称复数化并将其用作表名,使用一个名为inflection的库。另一方面,Prisma ORM 将模型名称映射到数据库中的表名建模您的数据。如果您希望更改 Sequelize 中的此默认行为,您可以强制表名等于模型名或直接提供表名。

// enforcing table name to be equal to model name
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define(
'Post',
{
// ... attributes
},
{
freezeTableName: true,
}
)
return Post
}
// providing the table name directly
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define(
'Post',
{
// ... attributes
},
{
tableName: 'Post',
}
)
return Post
}

时间戳

Sequelize 默认情况下会使用数据类型DataTypes.DATE自动向每个模型添加createdAtupdatedAt字段。您可以使用timestamps: false选项禁用模型的此功能。

sequelize.define(
'User',
{
// ... (attributes)
},
{
timestamps: false,
}
)

Prisma ORM 使您可以灵活地在模型中定义这些字段。您可以通过在模型中显式定义它们来添加createdAtupdatedAt字段。要在模型中设置createdAt字段,请向该列添加default(now())属性。为了设置updatedAt列,请通过向该列添加@updatedAt属性来更新您的模型。

model User {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

隐式多对多关系

类似于 Sequelize 中的 belongsToMany() 关联方法,Prisma ORM 允许您隐式地建模多对多关系。也就是说,在一个多对多关系中,您无需在模式中显式地管理关系表(有时也称为 JOIN 表)。以下是一个 Sequelize 的示例

module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: {
type: DataTypes.STRING,
allowNull: false,
},
content: {
type: DataTypes.STRING,
},
published: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
})
Post.associate = (models) => {
Post.belongsTo(models.User, {
foreignKey: 'authorId',
as: 'author',
})
Post.belongsToMany(models.Category, {
through: 'PostCategories',
as: 'categories',
})
}
return Post
}
module.exports = (sequelize, DataTypes) => {
const Category = sequelize.define('Category', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
})
Category.associate = (models) => {
Category.belongsToMany(models.Post, {
through: 'PostCategories',
as: 'posts',
})
}
return Category
}

当您启动应用程序时,Sequelize 会根据这些模型为您创建表

Executing (default): CREATE TABLE IF NOT EXISTS "PostCategories"
("createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"CategoryId" INTEGER REFERENCES "Categories" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
"PostId" INTEGER REFERENCES "Posts" ("id") ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY ("CategoryId","PostId"));

如果您使用 Prisma ORM 反向解析数据库,您将在 Prisma 模式中得到以下结果(请注意,一些关系字段名称已调整,使其看起来比反向解析得到的原始版本更友好)

model Categories {
id Int @id @default(autoincrement())
name String
createdAt DateTime
updatedAt DateTime
PostCategories PostCategories[]

@@map("category")
}

model PostCategories {
createdAt DateTime
updatedAt DateTime
CategoryId Int
PostId Int
Categories Categories @relation(fields: [CategoryId], references: [id])
Posts Posts @relation(fields: [PostId], references: [id])

@@id([CategoryId, PostId])
@@map("PostCategories")
}

model Posts {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
createdAt DateTime
updatedAt DateTime
authorId Int?
Users Users? @relation(fields: [authorId], references: [id])
PostCategories PostCategories[]

@@map("post")
}

在这个 Prisma 模式中,多对多关系通过关系表 PostCategories 显式建模

通过遵循 Prisma 关系表的约定,关系可以如下所示

model Categories {
id Int @id @default(autoincrement())
name String
posts Posts[]

@@map("category")
}

model Posts {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int?
author User? @relation(fields: [authorId], references: [id])
categories Categories[]

@@map("post")
}

这也会导致更符合人体工程学且更简洁的 Prisma Client API 来修改此关系中的记录,因为您有从 PostCategory 的直接路径(反之亦然),而无需首先遍历 PostCategories 模型。