如何在Prisma事务中结合数组实现批量Upsert操作
问题描述
我想在请求中传递最多包含6个元素的columns数组,通过Prisma实现批量Upsert操作(存在则更新,不存在则创建)。目前代码只能处理单个数组元素,而且创建新Column时触发了P2002主键约束错误。需要解决两个问题:
- 让更新与创建功能正常协同工作;
- 动态处理数组中的所有元素。
使用的数据模型
model Board { id String @id @unique @default(uuid()) title String User User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) userId Int columns Column[] tasks Task[] } model Column { id Int @id @default(autoincrement()) column String Board Board @relation(fields: [boardId], references: [id], onDelete: Cascade, onUpdate: Cascade) boardId String tasks Task[] }
当前函数代码
const columns = req.body.columns; const updateBoardData = prisma.board.update({ where: { id: req.body.id }, data: { title: req.body.title }, }); const upsertColumns = prisma.column.upsert({ where: { id: 65 }, // 仅处理id为65的元素 update: columns[0], create: columns[0], }); await prisma .$transaction([updateBoardData, upsertColumns]) .then((board) => { return res.status(201).json({ board }); }) .catch((err) => { console.log(err); return res.status(500).json(`${err}`); }) .finally(() => { return prisma.$disconnect(); });
出现的错误
PrismaClientKnownRequestError: Invalid `prisma.board.update()` invocation in C:\Users\Sébastien\Desktop\Kanban\Server\src\routes\boards\boards.controllers.js:170:42 167 const columns = req.body.columns; 168 const boardId = req.body.id; 169 → 170 const updateBoardData = prisma.board.update( Unique constraint failed on the constraint: `PRIMARY` at RequestHandler.handleRequestError (C:\Users\Sébastien\Desktop\Kanban\Server\node_modules\@prisma\client\runtime\index.js:34310:13) at RequestHandler.request (C:\Users\Sébastien\Desktop\Kanban\Server\node_modules\@prisma\client\runtime\index.js:34293:12) at async PrismaClient._request (C:\Users\Sébastien\Desktop\Kanban\Server\node_modules\@prisma\client\runtime\index.js:35273:16) at async Promise.all (index 0) at async updateBoard (C:\Users\Sébastien\Desktop\Kanban\Server\src\routes\boards\boards.controllers.js:189:5) { code: 'P2002', clientVersion: '4.6.0', meta: { target: 'PRIMARY' } }
Postman请求内容
{ "id": "fe6e843a-bcab-4404-85d0-8324794f04cc", "title": "TITLE UPDATED", "columns": [ { "id": 65, "column": "TODO", "boardId": "fe6e843a-bcab-4404-85d0-8324794f04cc"}, { "id":66, "column": "DONE", "boardId": "fe6e843a-bcab-4404-85d0-8324794f04cc"}, { "column": "NEW COLUMN", "boardId": "fe6e843a-bcab-4404-85d0-8324794f04cc" } ] }
更新:console.log(req.body)输出
{ id: 'fe6e843a-bcab-4404-85d0-8324794f04cc', title: 'TITLE UPDATED', columns: [ { id: 65, column: 'TODO', boardId: 'fe6e843a-bcab-4404-85d0-8324794f04cc' }, { id: 66, column: 'DONE', boardId: 'fe6e843a-bcab-4404-85d0-8324794f04cc' }, { column: 'NEW COLUMN', boardId: 'fe6e843a-bcab-4404-85d0-8324794f04cc' } ] }
问题分析
- P2002错误根源:Column的
id是自增主键,你在创建新列时如果手动指定id,一旦该ID已存在就会触发主键冲突。另外当前代码硬编码了id:65,完全没处理数组中其他元素。 - 批量处理缺失:代码只处理了
columns[0],没有遍历数组生成对应upsert操作。
解决方案
核心思路
- 对每个Column元素,根据是否存在
id判断是更新还是创建; - 创建新列时不传入
id,依赖数据库自增逻辑避免冲突; - 遍历数组生成批量upsert操作,放入事务保证原子性。
修改后的完整代码
const { id: boardId, title, columns } = req.body; // 校验列数量不超过6个 if (columns.length > 6) { return res.status(400).json({ error: "最多只能传入6个列" }); } // 更新Board的操作 const updateBoard = prisma.board.update({ where: { id: boardId }, data: { title }, }); // 批量生成Column的upsert操作 const upsertColumnOperations = columns.map(column => { return prisma.column.upsert({ // 有id则用该id查询,无id则用一个不存在的id确保走创建逻辑 where: { id: column.id || -1 }, // 更新时只修改需要的字段,避免覆盖自增id update: column.id ? { column: column.column, boardId: column.boardId } : {}, // 创建时不传入id,让数据库自动生成 create: { column: column.column, boardId: boardId } }); }); try { // 执行事务,保证操作原子性 await prisma.$transaction([updateBoard, ...upsertColumnOperations]); // 查询返回包含完整列数据的Board const fullBoard = await prisma.board.findUnique({ where: { id: boardId }, include: { columns: true } }); return res.status(200).json({ board: fullBoard }); } catch (err) { console.error(err); return res.status(500).json({ error: err.message }); } finally { await prisma.$disconnect(); }
关键优化点
- 动态处理每个列:通过
map遍历数组,根据元素是否有id自动适配upsert逻辑; - 避免主键冲突:创建新列时不指定
id,完全依赖数据库自增机制; - 事务原子性:所有操作要么全部成功,要么全部失败,防止数据不一致;
- 返回完整数据:更新后查询包含Columns的完整Board,给前端返回最新状态;
- 参数校验:添加列数量限制,符合需求中的最多6个元素要求。
内容的提问来源于stack exchange,提问作者Sebastien Stordeur
相关产品推荐
相关产品推荐

