从 TypeORM 迁移
本指南介绍如何从 TypeORM 迁移到 Prisma ORM。它使用TypeORM Express 示例的扩展版本作为示例项目来演示迁移步骤。您可以在GitHub上找到用于本指南的示例。
本迁移指南使用 PostgreSQL 作为示例数据库,但它同样适用于任何其他Prisma ORM 支持的关系型数据库。
您可以在Prisma ORM 与 TypeORM页面上了解 Prisma ORM 与 TypeORM 的比较。
迁移过程概述
请注意,从 TypeORM 迁移到 Prisma ORM 的步骤始终相同,无论您构建的是哪种应用程序或 API 层
- 安装 Prisma CLI
- 自省您的数据库
- 创建基线迁移
- 安装 Prisma Client
- 逐渐用 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。它有四个模型/实体
- User.ts
- Post.ts
- Profile.ts
- Category.ts
@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
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column({ nullable: true })
content: string
@Column({ default: false })
published: boolean
@ManyToOne((type) => User, (user) => user.posts)
author: User
@ManyToMany((type) => Category, (category) => category.posts)
@JoinTable()
categories: Category[]
}
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number
@Column({ nullable: true })
bio: string
@OneToOne((type) => User, (user) => user.profile)
@JoinColumn()
user: User
}
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToMany((type) => Post, (post) => post.categories)
posts: Post[]
}
这些模型具有以下关系
- 1-1:
User
↔Profile
- 1-n:
User
↔Post
- m-n:
Post
↔Category
相应的表已使用生成的 TypeORM 迁移创建。
展开以查看迁移的详细信息
迁移已使用以下命令创建
typeorm migration:generate -n Init
这将创建以下迁移文件
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 架构目前如下所示
// 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
字段调整为当前使用的数据库
- PostgreSQL
- MySQL
- Microsoft SQL Server
- SQLite
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
datasource db {
provider = "sqlserver"
url = env("DATABASE_URL")
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
完成之后,您可以在.env
文件中配置您的数据库连接 URL。以下是 TypeORM 中的数据库连接如何映射到 Prisma ORM 使用的连接 URL 格式
- PostgreSQL
- MySQL
- Microsoft SQL Server
- SQLite
假设您在ormconfig.json
中具有以下数据库连接详细信息
{
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "alice",
"password": "myPassword42",
"database": "blog-typeorm"
}
相应的连接 URL 在 Prisma ORM 中将如下所示
DATABASE_URL="postgresql://alice:myPassword42@localhost:5432/blog-typeorm"
请注意,您可以选择通过将schema
参数附加到连接 URL 来配置 PostgreSQL 架构。
DATABASE_URL="postgresql://alice:myPassword42@localhost:5432/blog-typeorm?schema=myschema"
如果未提供,将使用名为public
的默认架构。
假设您在ormconfig.json
中具有以下数据库连接详细信息
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "alice",
"password": "myPassword42",
"database": "blog-typeorm"
}
相应的连接 URL 在 Prisma 中将如下所示
DATABASE_URL="mysql://alice:myPassword42@localhost:3306/blog-typeorm"
假设您在ormconfig.json
中具有以下数据库连接详细信息
{
"type": "mssql",
"host": "localhost",
"port": 1433,
"username": "alice",
"password": "myPassword42",
"database": "blog-typeorm"
}
相应的连接 URL 在 Prisma 中将如下所示
DATABASE_URL="sqlserver://127.0.0.1:1433;database=blog-typeorm;user=alice;password=myPassword42;trustServerCertificate=true"
假设您在ormconfig.json
中具有以下数据库连接详细信息
{
"type": "sqlite",
"database": "blog-typeorm"
}
相应的连接 URL 在 Prisma 中将如下所示
DATABASE_URL="file:./blog-typeorm.db"
2.3. 使用 Prisma ORM 自省您的数据库
有了您的连接 URL,您就可以自省您的数据库以生成您的 Prisma 模型
npx prisma db pull
这将创建以下 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 架构版本,它解决了这些问题
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
并将其从文件中导出,以便您以后可以在路由处理程序中使用它。
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
处理程序当前按如下方式实现
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 实现相同路由的方式
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
处理程序当前按如下方式实现
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,该路由按如下方式实现
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
处理程序当前按如下方式实现
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,该路由按如下方式实现
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
处理程序当前按如下方式实现
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,该路由按如下方式实现
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
处理程序当前按如下方式实现
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,该路由按如下方式实现
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
处理程序当前按如下方式实现
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,该路由按如下方式实现
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
处理程序当前按如下方式实现
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,该路由按如下方式实现
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 可以通过将关系建模为 隐式多对多关系 来使其变得不那么冗长。在这种情况下,查询将如下所示
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 架构中获得以下结果(请注意,一些关系字段名称已被调整,使其看起来比内省的原始版本更友好)
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 关系表的约定,关系可以如下所示
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 来修改此关系中的记录,因为您有一条从 Post
到 Category
的直接路径(反之亦然),而不是需要先遍历 PostToCategories
模型。
如果您的数据库提供商要求表具有主键,那么您必须使用显式语法并手动创建具有主键的联接模型。这是因为 Prisma ORM 为使用隐式语法创建的多对多关系创建的关系表(JOIN 表)(通过 @relation
表达)没有主键。