跳到主要内容

从 TypeORM 迁移

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

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

您可以在Prisma ORM 与 TypeORM页面上了解 Prisma ORM 与 TypeORM 的比较。

迁移过程概述

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

  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 用于字段名称

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

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

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

这是一个调整后的 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 将自动为您创建以下关系表

-- 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 为使用隐式语法创建的多对多关系创建的关系表(JOIN 表)(通过 @relation 表达)没有主键。