从 Sequelize 迁移
本指南介绍如何从 Sequelize 迁移到 Prisma ORM。它使用 Sequelize Express 示例 的扩展版本作为 示例项目 来演示迁移步骤。您可以在 GitHub 上找到用于本指南的示例。
本迁移指南使用 PostgreSQL 作为示例数据库,但它同样适用于任何其他 Prisma 支持的 关系数据库。
您可以在 Prisma ORM 与 Sequelize 页面上了解 Prisma ORM 如何与 Sequelize 相比。
迁移过程概述
请注意,从 Sequelize 迁移到 Prisma ORM 的步骤始终相同,无论您构建的是什么类型的应用程序或 API 层
- 安装 Prisma CLI
- 自省您的数据库
- 创建基线迁移
- 安装 Prisma 客户端
- 逐渐将 Sequelize 查询替换为 Prisma 客户端
无论您构建的是 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。它有四个模型/实体
- User.js
- Post.js
- Profile.js
- Category.js
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
}
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 Profile = sequelize.define('Profile', {
bio: {
type: DataTypes.STRING,
allowNull: false,
},
})
Profile.associate = (models) => {
Profile.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user',
})
}
return Profile
}
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
}
这些模型具有以下关系
- 1-1:
User
↔Profile
- 1-n:
User
↔Post
- m-n:
Post
↔Category
相应的表已使用生成的 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 架构当前如下所示
// 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。以下是 Sequelize 中的数据库连接如何映射到 Prisma ORM 使用的连接 URL 格式
- PostgreSQL
- MySQL
- Microsoft SQL Server
- SQLite
假设您在 src/models/index.js
中具有以下数据库连接详细信息
const sequelize = new Sequelize('blog-sequelize', 'alice', 'myPassword42', {
host: 'localhost',
dialect: 'postgres',
})
相应的连接 URL 在 Prisma ORM 中如下所示
DATABASE_URL="postgresql://alice:myPassword42@localhost:5432/blog-sequelize"
请注意,您可以选择通过将 schema
参数附加到连接 URL 来配置 PostgreSQL 架构。
DATABASE_URL="postgresql://alice:myPassword42@localhost:5432/blog-sequelize?schema=myschema"
如果未提供,则使用名为 public
的默认架构。
假设您在 src/models/index.js
中具有以下数据库连接详细信息
const sequelize = new Sequelize('blog-sequelize', 'alice', 'myPassword42', {
host: 'localhost',
dialect: 'postgres',
})
相应的连接 URL 在 Prisma 中如下所示
DATABASE_URL="mysql://alice:myPassword42@localhost:3306/blog-sequelize"
假设您在 src/models/index.js
中具有以下数据库连接详细信息
const sequelize = new Sequelize('blog-sequelize', 'alice', 'myPassword42', {
host: 'localhost',
dialect: 'mssql',
})
相应的连接 URL 在 Prisma 中如下所示
DATABASE_URL="sqlserver://127.0.0.1:1433;database=blog-sequelize;user=alice;password=myPassword42;trustServerCertificate=true"
假设您在 src/models/index.js
中具有以下数据库连接详细信息
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: '../../blog-sequelize.sqlite',
})
相应的连接 URL 在 Prisma 中如下所示
DATABASE_URL="file:./blog-sequelize.db"
2.3. 使用 Prisma ORM 自省您的数据库
在您的连接 URL 就绪后,您可以 自省 您的数据库以生成 Prisma 模型
npx prisma db pull
这将创建以下 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 迁移来发展您的数据库架构,您需要 对您的数据库进行基线。
首先,创建一个 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. 调整 createdAt
和 updatedAt
字段
生成的 Prisma 模型代表您的数据库表,是您使用 Prisma Client API 进行编程的基础,它允许您向数据库发送查询。您需要调整模型中的 createdAt
和 updatedAt
字段。Sequelize 在数据库中创建表时不会为 createdAt
添加 DEFAULT
约束。因此,您需要分别在 createdAt
和 updatedAt
列中添加 @default(now())
和 @updatedAt
属性。要了解更多关于 Prisma ORM 如何做到这一点,您可以阅读更多关于 @default(now())
和 @updatedAt
的信息。我们更新的 schema 将如下所示
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 schema(可选)
通过内省生成的模型当前完全映射到您的数据库表。在本节中,您将了解如何调整 Prisma 模型的命名以符合 Prisma ORM 的命名约定。
所有这些调整都是完全可选的,如果您现在不想进行任何调整,可以自由跳过下一步。您可以稍后随时返回并进行调整。
与当前 Prisma 模型的 snake_case 表示法不同,Prisma ORM 的命名约定是
- 模型名称为 PascalCase
- 字段名称为 camelCase
您可以使用 @@map
和 @map
将 Prisma 模型和字段名称映射到底层数据库中现有的表和列名,从而调整命名。
还要注意,您可以重命名 关系字段 以优化您稍后将用于向数据库发送查询的 Prisma Client API。例如,虽然我们将 Posts
模型名称改为 Post
,但 user
模型上的 posts
字段是一个列表,因此将其命名为 posts
更合理,以表明它是复数形式。
Sequelize 生成一个 SequelizeMeta
模型,该模型由库内部使用,不需要。因此,您需要从 schema 中手动删除它。
以下是对 Prisma schema 的调整版本,它解决了这些问题
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,以便开始用 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
并将其从文件中导出,以便您稍后在路由处理程序中使用它。
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
module.exports = prisma
控制器文件中的导入如下所示
const { Post, User, Category } = require('../models')
const { Op } = require('sequelize')
const { User } = require('../models')
在从 Sequelize 迁移到 Prisma 时,您将更新控制器导入。
const prisma = require('../prisma')
const prisma = require('../prisma')
4.1. 替换 GET
请求中的查询
REST API 有四个接受 GET
请求的路由
/feed
:返回所有已发布的帖子/filterPosts?searchString=SEARCH_STRING
:按SEARCH_STRING
过滤返回的帖子/post/:postId
:返回特定帖子/authors
:返回作者列表
让我们深入了解实现这些请求的路由处理程序。
/feed
/feed
处理程序当前的实现方式如下
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
对象都包含与之关联的 author
和 category
的关系。使用 Sequelize,包含关系不是类型安全的。例如,如果检索到的关系中存在拼写错误,您的数据库查询将在运行时失败 - JavaScript 编译器不会提供任何安全性。
以下是使用 Prisma Client 实现同一路由的方式
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
处理程序当前的实现方式如下
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,路由的实现方式如下
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
处理程序当前的实现方式如下
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,路由的实现方式如下
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
处理程序当前的实现方式如下
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,路由的实现方式如下
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
处理程序当前的实现方式如下
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,路由的实现方式如下
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
处理程序当前的实现方式如下
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,路由的实现方式如下
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
处理程序当前的实现方式如下
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,路由的实现方式如下
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 更简洁。在这种情况下,查询将如下所示
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
默认情况下会自动将 createdAt
和 updatedAt
字段添加到每个模型中。您可以使用 timestamps: false
选项为模型禁用此功能。
sequelize.define(
'User',
{
// ... (attributes)
},
{
timestamps: false,
}
)
Prisma ORM 提供了在模型中定义这些字段的灵活性。您可以通过在模型中显式定义它们来添加 createdAt
和 updatedAt
字段。要在模型中设置 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 允许您隐式建模多对多关系。也就是说,在您的 schema 中,您不必显式管理 关系表(有时也称为 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 schema 中得到以下结果(请注意,与从内省中获得的原始版本相比,一些关系字段名已被调整为更友好)
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 schema 中,多对多关系是通过关系表 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 更符合人体工程学,并且在修改此关系中的记录时更简洁,因为您拥有从 Post
到 Category
的直接路径(反之亦然),而无需先遍历 PostCategories
模型。