Prisma中如何持久化保存一对多关联列表的自定义排序
Prisma 一对多关联下自定义排序持久化实现方案
核心思路很直接:关联关系本身不存储顺序信息,你需要在「多」端的模型上新增独立的排序字段存储自定义顺序值,所有读写逻辑围绕这个字段处理即可。
1. 修改Prisma Schema新增排序字段
在Book模型上新增Int类型的排序字段,同时加联合唯一约束避免同用户下出现重复排序值:
model User { id Int @id @default(autoincrement()) // 你原有User的其他字段 books Book[] } model Book { id Int @id @default(autoincrement()) // 你原有Book的其他标量字段,比如title、author之类 createdAt DateTime @default(now()) // 新增:存储图书在所属用户下的展示排序值 displayOrder Int userId Int user User @relation(fields: [userId], references: [id], onDelete: Cascade) // 联合唯一约束:同一个用户下的排序值不重复 @@unique([userId, displayOrder]) }
改完之后执行prisma migrate dev生成数据库迁移。
2. 初始化历史数据的排序值
给已经存在的旧图书数据补全排序值,默认沿用你之前的创建时间排序规则即可,跑一次性脚本完成初始化:
import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() async function initBookDisplayOrder() { // 遍历所有用户,按原有创建时间顺序给图书分配从0开始的排序值 const allUsers = await prisma.user.findMany({ include: { books: { orderBy: { createdAt: 'asc' } } } }) for (const user of allUsers) { const updateTasks = user.books.map((book, idx) => prisma.book.update({ where: { id: book.id }, data: { displayOrder: idx } }) ) await prisma.$transaction(updateTasks) } } initBookDisplayOrder().finally(() => prisma.$disconnect())
3. 查询时按自定义排序字段返回
原来查询默认按createdAt排序,现在显式指定按displayOrder升序查询即可,返回的数组就是用户自定义的顺序:
const userData = await prisma.user.findUnique({ where: { id: targetUserId }, include: { books: { orderBy: { displayOrder: 'asc' } // 你需要select的其他图书字段 } } })
4. 实现顺序调整逻辑
前端一般拖拽排序后会返回调整完成后的完整图书ID顺序列表,后端做两层处理:先做权限校验,再开事务批量更新排序值,避免中间出错导致顺序错乱:
/** * 更新用户的图书展示顺序 * @param userId 当前操作的用户ID * @param orderedBookIds 调整顺序后的图书ID数组,数组顺序即为目标展示顺序 */ async function updateBookSort(userId: number, orderedBookIds: number[]) { // 校验所有传入的图书都属于当前用户,防止越权修改 const validBooks = await prisma.book.findMany({ where: { userId, id: { in: orderedBookIds } }, select: { id: true } }) if (validBooks.length !== orderedBookIds.length) { throw new Error('非法操作:包含不属于当前用户的图书') } // 开事务批量更新每本书的排序值 await prisma.$transaction( orderedBookIds.map((bookId, index) => prisma.book.update({ where: { id: bookId }, data: { displayOrder: index } }) ) ) }
其他场景的适配
- 新增图书时:查询当前用户下最大的
displayOrder值,给新书赋值为该值+1即可,新书默认排在列表末尾,符合常规交互习惯 - 删除图书时:不需要特意调整剩余图书的排序值,排序只依赖字段的相对大小,哪怕中间值有断层也不影响orderBy的结果,能省掉不必要的数据库写操作
- 如果要支持置顶/置底操作:直接把对应图书的
displayOrder设为比当前最小值更小/比最大值更大的数即可,不需要全量更新所有图书的排序值
内容的提问来源于stack exchange,提问作者xmuhs
相关产品推荐
相关产品推荐

