跳至主要内容

迁移自 TypeORM

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

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

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

迁移过程概述

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

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

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

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

示例项目的概述

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

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number

@Column({ nullable: true })
name: string

@Column({ unique: true })
email: string

@OneToMany((type) => Post, (post) => post.author)
posts: Post[]

@OneToOne((type) => Profile, (profile) => profile.user, { cascade: true })
profile: Profile
}

这些模型具有以下关系

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

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

展开以查看迁移的详细信息

迁移是使用以下方式创建的

typeorm migration:generate -n Init

这创建了以下迁移文件

migrations/1605698662257-Init.ts
import { MigrationInterface, QueryRunner } from 'typeorm'

export class Init1605698662257 implements MigrationInterface {
name = 'Init1605698662257'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "profile" ("id" SERIAL NOT NULL, "bio" character varying, "userId" integer, CONSTRAINT "REL_a24972ebd73b106250713dcddd" UNIQUE ("userId"), CONSTRAINT "PK_3dd8bfc97e4a77c70971591bdcb" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE TABLE "user" ("id" SERIAL NOT NULL, "name" character varying, "email" character varying NOT NULL, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE TABLE "post" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "content" character varying, "published" boolean NOT NULL DEFAULT false, "authorId" integer, CONSTRAINT "PK_be5fda3aac270b134ff9c21cdee" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE TABLE "category" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_9c4e4a89e3674fc9f382d733f03" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE TABLE "post_categories_category" ("postId" integer NOT NULL, "categoryId" integer NOT NULL, CONSTRAINT "PK_91306c0021c4901c1825ef097ce" PRIMARY KEY ("postId", "categoryId"))`
)
await queryRunner.query(
`CREATE INDEX "IDX_93b566d522b73cb8bc46f7405b" ON "post_categories_category" ("postId") `
)
await queryRunner.query(
`CREATE INDEX "IDX_a5e63f80ca58e7296d5864bd2d" ON "post_categories_category" ("categoryId") `
)
await queryRunner.query(
`ALTER TABLE "profile" ADD CONSTRAINT "FK_a24972ebd73b106250713dcddd9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
)
await queryRunner.query(
`ALTER TABLE "post" ADD CONSTRAINT "FK_c6fb082a3114f35d0cc27c518e0" FOREIGN KEY ("authorId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
)
await queryRunner.query(
`ALTER TABLE "post_categories_category" ADD CONSTRAINT "FK_93b566d522b73cb8bc46f7405bd" FOREIGN KEY ("postId") REFERENCES "post"("id") ON DELETE CASCADE ON UPDATE NO ACTION`
)
await queryRunner.query(
`ALTER TABLE "post_categories_category" ADD CONSTRAINT "FK_a5e63f80ca58e7296d5864bd2d3" FOREIGN KEY ("categoryId") REFERENCES "category"("id") ON DELETE CASCADE ON UPDATE NO ACTION`
)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "post_categories_category" DROP CONSTRAINT "FK_a5e63f80ca58e7296d5864bd2d3"`
)
await queryRunner.query(
`ALTER TABLE "post_categories_category" DROP CONSTRAINT "FK_93b566d522b73cb8bc46f7405bd"`
)
await queryRunner.query(
`ALTER TABLE "post" DROP CONSTRAINT "FK_c6fb082a3114f35d0cc27c518e0"`
)
await queryRunner.query(
`ALTER TABLE "profile" DROP CONSTRAINT "FK_a24972ebd73b106250713dcddd9"`
)
await queryRunner.query(`DROP INDEX "IDX_a5e63f80ca58e7296d5864bd2d"`)
await queryRunner.query(`DROP INDEX "IDX_93b566d522b73cb8bc46f7405b"`)
await queryRunner.query(`DROP TABLE "post_categories_category"`)
await queryRunner.query(`DROP TABLE "category"`)
await queryRunner.query(`DROP TABLE "post"`)
await queryRunner.query(`DROP TABLE "user"`)
await queryRunner.query(`DROP TABLE "profile"`)
}
}

如前所述,本指南是 TypeORM Express 示例的扩展变体,并使用相同的文件夹结构。路由处理程序位于 src/controller 目录中。从那里,它们被拉入一个中央 src/routes.ts 文件,该文件用于在 src/index.ts 中设置所需的路由。

└── blog-typeorm
├── ormconfig.json
├── package.json
├── src
│   ├── controllers
│   │   ├── AddPostToCategoryAction.ts
│   │   ├── CreateDraftAction.ts
│   │   ├── CreateUserAction.ts
│   │   ├── FeedAction.ts
│   │   ├── FilterPostsAction.ts
│   │   ├── GetPostByIdAction.ts
│   │   └── SetBioForUserAction.ts
│   ├── entity
│   │   ├── Category.ts
│   │   ├── Post.ts
│   │   ├── Profile.ts
│   │   └── User.ts
│   ├── index.ts
│   ├── migration
│   │   └── 1605698662257-Init.ts
│   └── routes.ts
└── tsconfig.json

步骤 1. 安装 Prisma CLI

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

npm install prisma --save-dev

步骤 2. 内省您的数据库

2.1. 设置 Prisma ORM

在您内省数据库之前,您需要设置您的 Prisma 模式 并将 Prisma 连接到您的数据库。在您的终端中运行以下命令以创建一个基本的 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 字段调整为您当前使用的数据库。


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

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


假设您在 ormconfig.json 中具有以下数据库连接详细信息


ormconfig.json
{
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "alice",
"password": "myPassword42",
"database": "blog-typeorm"
}

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


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

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


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

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


2.3. 使用 Prisma ORM 自省数据库

有了连接 URL 后,您可以 自省 数据库以生成 Prisma 模型。

npx prisma db pull

这将创建以下 Prisma 模型:

prisma/schema.prisma
model typeorm_migrations {
id Int @id @default(autoincrement())
timestamp Int
name String

@@map("_typeorm_migrations")
}

model category {
id Int @id @default(autoincrement())
name String
post_categories_category post_categories_category[]
}

model post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int?
user user? @relation(fields: [authorId], references: [id])
post_categories_category post_categories_category[]
}

model post_categories_category {
postId Int
categoryId Int
category category @relation(fields: [categoryId], references: [id])
post post @relation(fields: [postId], references: [id])

@@id([postId, categoryId])
@@index([postId], name: "IDX_93b566d522b73cb8bc46f7405b")
@@index([categoryId], name: "IDX_a5e63f80ca58e7296d5864bd2d")
}

model profile {
id Int @id @default(autoincrement())
bio String?
userId Int? @unique
user user? @relation(fields: [userId], references: [id])
}

model user {
id Int @id @default(autoincrement())
name String?
email String @unique
post post[]
profile profile?
}

生成的 Prisma 模型代表您的数据库表,并且是您程序化 Prisma Client API 的基础,它允许您向数据库发送查询。

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. 调整 Prisma 模式(可选)

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

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

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

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

您可以通过映射 Prisma 模型和字段名称到基础数据库中现有的表和列名称来调整命名,方法是使用 @@map@map

另请注意,您可以重命名 关系字段 以优化您稍后将用于向数据库发送查询的 Prisma Client API。例如,user 模型上的 post 字段是一个列表,因此此字段的更好名称是 posts 以指示它是复数形式。

您可以进一步完全删除代表 TypeORM 迁移表的模型(此处称为 _typeorm_migrations)从 Prisma 模式中。

这是一个调整后的 Prisma 模式版本,它解决了这些问题:

prisma/schema.prisma
model Category {
id Int @id @default(autoincrement())
name String
postsToCategories PostToCategories[]

@@map("category")
}

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

@@map("post")
}

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

@@id([postId, categoryId])
@@index([postId], name: "IDX_93b566d522b73cb8bc46f7405b")
@@index([categoryId], name: "IDX_a5e63f80ca58e7296d5864bd2d")
@@map("post_categories_category")
}

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

@@map("profile")
}

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

@@map("user")
}

步骤 3. 安装 Prisma Client

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

npm install @prisma/client

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

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

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

touch src/prisma.ts

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

src/prisma.ts
import { PrismaClient } from '@prisma/client'

export const prisma = new PrismaClient()

4.1. 替换 GET 请求中的查询

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

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

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

/feed

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

src/controllers/FeedAction.ts
import { getManager } from 'typeorm'
import { Post } from '../entity/Post'

export async function feedAction(req, res) {
const postRepository = getManager().getRepository(Post)

const publishedPosts = await postRepository.find({
where: { published: true },
relations: ['author'],
})

res.send(publishedPosts)
}

请注意,每个返回的 Post 对象都包含与其关联的 author 的关系。使用 TypeORM,包含关系是不安全的。例如,如果检索到的关系中存在拼写错误,您的数据库查询只会在运行时失败——TypeScript 编译器在此处不提供任何安全性。

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

src/controllers/FeedAction.ts
import { prisma } from '../prisma'

export async function feedAction(req, res) {
const publishedPosts = await prisma.post.findMany({
where: { published: true },
include: { author: true },
})

res.send(publishedPosts)
}

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

/filterPosts?searchString=SEARCH_STRING

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

src/controllers/FilterPostsActions.ts
import { getManager, Like } from 'typeorm'
import { Post } from '../entity/Post'

export async function filterPostsAction(req, res) {
const { searchString } = req.query
const postRepository = getManager().getRepository(Post)

const filteredPosts = await postRepository.find({
where: [
{ title: Like(`%${searchString}%`) },
{ content: Like(`%${searchString}%`) },
],
})

res.send(filteredPosts)
}

使用 Prisma ORM,该路由实现如下:

src/controllers/FilterPostsActions.ts
import { prisma } from '../prisma'

export async function filterPostsAction(req, res) {
const { searchString } = req.query

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

res.send(filteredPosts)
}

请注意,TypeORM 默认情况下使用隐式 OR 运算符组合多个 where 条件。另一方面,Prisma ORM 使用隐式 AND 运算符组合多个 where 条件,因此在本例中,Prisma Client 查询需要明确 OR

/post/:postId

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

src/controllers/GetPostByIdAction.ts
import { getManager } from 'typeorm'
import { Post } from '../entity/Post'

export async function getPostByIdAction(req, res) {
const { postId } = req.params
const postRepository = getManager().getRepository(Post)

const post = await postRepository.findOne(postId)

res.send(post)
}

使用 Prisma ORM,该路由实现如下:

src/controllers/GetPostByIdAction.ts
import { prisma } from '../prisma'

export async function getPostByIdAction(req, res) {
const { postId } = req.params

const post = await prisma.post.findUnique({
where: { id: postId },
})

res.send(post)
}

4.2. 替换 POST 请求中的查询

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

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

/user

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

src/controllers/CreateUserAction.ts
import { getManager } from 'typeorm'
import { User } from '../entity/User'

export async function createUserAction(req, res) {
const { name, email } = req.body

const userRepository = getManager().getRepository(User)

const newUser = new User()
newUser.name = name
newUser.email = email
userRepository.save(newUser)

res.send(newUser)
}

使用 Prisma ORM,该路由实现如下:

src/controllers/CreateUserAction.ts
import { prisma } from '../prisma'

export async function createUserAction(req, res) {
const { name, email } = req.body

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

res.send(newUser)
}

/post

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

src/controllers/CreateDraftAction.ts
import { getManager } from 'typeorm'
import { Post } from '../entity/Post'
import { User } from '../entity/User'

export async function createDraftAction(req, res) {
const { title, content, authorEmail } = req.body

const userRepository = getManager().getRepository(User)
const user = await userRepository.findOne({ email: authorEmail })

const postRepository = getManager().getRepository(Post)

const newPost = new Post()
newPost.title = title
newPost.content = content
newPost.author = user
postRepository.save(newPost)

res.send(newPost)
}

使用 Prisma ORM,该路由实现如下:

src/controllers/CreateDraftAction.ts
import { prisma } from '../prisma'

export async function createDraftAction(req, res) {
const { title, content, authorEmail } = req.body

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

res.send(newPost)
}

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

/user/:userId/profile

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

src/controllers/SetBioForUserAction.ts.ts
import { getManager } from 'typeorm'
import { Profile } from '../entity/Profile'
import { User } from '../entity/User'

export async function setBioForUserAction(req, res) {
const { userId } = req.params
const { bio } = req.body

const userRepository = getManager().getRepository(User)
const user = await userRepository.findOne(userId, {
relations: ['profile'],
})

const profileRepository = getManager().getRepository(Profile)
user.profile.bio = bio

profileRepository.save(user.profile)

res.send(user)
}

使用 Prisma ORM,该路由实现如下:

src/controllers/SetBioForUserAction.ts.ts
import { prisma } from '../prisma'

export async function setBioForUserAction(req, res) {
const { userId } = req.params
const { bio } = req.body

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

res.send(user)
}

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/AddPostToCategoryAction.ts
import { getManager } from 'typeorm'
import { Post } from '../entity/Post'
import { Category } from '../entity/Category'

export async function addPostToCategoryAction(req, res) {
const { postId, categoryId } = req.query

const postRepository = getManager().getRepository(Post)
const post = await postRepository.findOne(postId, {
relations: ['categories'],
})

const categoryRepository = getManager().getRepository(Category)
const category = await categoryRepository.findOne(categoryId)

post.categories.push(category)
postRepository.save(post)

res.send(post)
}

使用 Prisma ORM,该路由实现如下:

src/controllers/AddPostToCategoryAction.ts
import { prisma } from '../prisma'

export async function addPostToCategoryAction(req, res) {
const { postId, categoryId } = req.query

const post = await prisma.post.update({
data: {
postsToCategories: {
create: {
category: {
connect: { id: categoryId },
},
},
},
},
where: {
id: postId,
},
})

res.send(post)
}

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

src/controllers/AddPostToCategoryAction.ts
const post = await prisma.post.update({
data: {
categories: {
connect: { id: categoryId },
},
},
where: { id: postId },
})

更多

隐式多对多关系

类似于 TypeORM 中的 @manyToMany 装饰器,Prisma ORM 允许您 隐式建模多对多关系。也就是说,在多对多关系中,您不必显式地在模式中管理 关系表(有时也称为 JOIN 表)。以下是一个 TypeORM 的示例:

import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from 'typeorm'
import { Category } from './Category'

@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number

@ManyToMany((type) => Category, (category) => category.posts)
@JoinTable()
categories: Category[]
}
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm'
import { Post } from './Post'

@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number

@ManyToMany((type) => Post, (post) => post.categories)
posts: Post[]
}

如果基于这些模型生成并运行 TypeORM 的迁移,TypeORM 将自动为您创建以下关系表:

-- Table Definition ----------------------------------------------
CREATE TABLE post_categories_category (
"postId" integer REFERENCES post(id) ON DELETE CASCADE,
"categoryId" integer REFERENCES category(id) ON DELETE CASCADE,
CONSTRAINT "PK_91306c0021c4901c1825ef097ce" PRIMARY KEY ("postId", "categoryId")
);

-- Indices -------------------------------------------------------
CREATE UNIQUE INDEX "PK_91306c0021c4901c1825ef097ce" ON post_categories_category("postId" int4_ops,"categoryId" int4_ops);
CREATE INDEX "IDX_93b566d522b73cb8bc46f7405b" ON post_categories_category("postId" int4_ops);
CREATE INDEX "IDX_a5e63f80ca58e7296d5864bd2d" ON post_categories_category("categoryId" int4_ops);

如果使用 Prisma ORM 自省数据库,您将在 Prisma 模式中获得以下结果(请注意,与自省的原始版本相比,一些关系字段名称已调整为更友好)。

schema.prisma
model Category {
id Int @id @default(autoincrement())
name String
postsToCategories PostToCategories[]

@@map("category")
}

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

@@map("post")
}

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

@@id([postId, categoryId])
@@index([postId], name: "IDX_93b566d522b73cb8bc46f7405b")
@@index([categoryId], name: "IDX_a5e63f80ca58e7296d5864bd2d")
@@map("post_categories_category")
}

在此 Prisma 模式中,多对多关系通过关系表 PostToCategories显式建模。

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

schema.prisma
model Category {
id Int @id @default(autoincrement())
name String
posts Post[]

@@map("category")
}

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

@@map("post")
}

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

警告

如果您的数据库提供商要求表具有主键,则您必须使用显式语法,并手动创建具有主键的连接模型。这是因为 Prisma ORM(通过@relation表示)为使用隐式语法的多对多关系创建的关系表(JOIN 表)没有主键。