You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Prisma事务中结合数组实现批量Upsert操作

问题描述

我想在请求中传递最多包含6个元素的columns数组,通过Prisma实现批量Upsert操作(存在则更新,不存在则创建)。目前代码只能处理单个数组元素,而且创建新Column时触发了P2002主键约束错误。需要解决两个问题:

  1. 让更新与创建功能正常协同工作;
  2. 动态处理数组中的所有元素。

使用的数据模型

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'
    }
  ]
}

问题分析

  1. P2002错误根源:Column的id是自增主键,你在创建新列时如果手动指定id,一旦该ID已存在就会触发主键冲突。另外当前代码硬编码了id:65,完全没处理数组中其他元素。
  2. 批量处理缺失:代码只处理了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 14:35:20