Prisma 事务中如何基于首表插入结果动态关联次表记录?

13次阅读

Prisma 事务中如何基于首表插入结果动态关联次表记录?

本文详解 prisma v5 中使用 `$transaction` 回调模式实现跨表原子操作:先创建主记录(如 account),再用其生成的 id 创建关联记录(如 transaction),避免硬编码 id 引用,确保数据一致性与事务安全性。

在 Prisma 中,直接在数组式事务($transaction([…]))中引用前一步操作返回的 ID 是不可行的——因为数组内每个 promise 是并行提交、彼此隔离的,无法访问上一步的返回值。你代码中尝试使用的 prisma.account.fields.id 并非运行时生成的 ID,而是 Prisma 的元数据字段定义,会导致类型错误或运行时失败

✅ 正确做法是改用 回调式事务($transaction(async (tx) => {…})),它提供一个事务上下文 tx,支持顺序执行、变量传递和错误回滚:

const result = await prisma.$transaction(async (tx) => {   // 第一步:创建账户,获取实际生成的 accountId   const account = await tx.account.create({     data: {       accountCode: Number(accountCode),       name: name?.trim() ?? '',       type,       description: description ?? '',       balance: Number(balance),       status,       branchId,       createdById: req.user.id,     },   });    // 第二步:基于 account.id 创建交易记录(仅当余额 > 0)   if (Number(balance) > 0) {     await tx.transaction.create({       data: {         type: 'OPENING_BALANCE',         amount: number(balance),         reference: uuidv4(),         description: `Account opening balance for ${account.name} created by ${req.user.name}`,         status: 'ACTIVE',         branchId,         accountId: account.id, // ✅ 安全引用刚创建的 ID         createdById: req.user.id,       },     });   }    // 可选:返回关键数据便于后续处理   return { account }; });

? 关键优势

  • 原子性保障:任一操作失败,整个事务自动回滚;
  • 类型安全:account.id 是 String 或 number(取决于模型定义),idetypescript 可精准推导;
  • 逻辑清晰:条件分支(如 if (balance > 0))自然嵌入,无需依赖空数组技巧。

⚠️ 注意事项:

  • 不要混用 create + 外键手动赋值(如 accountId: …)与 connect/create 关系嵌套——二者语义不同。若 transaction 模型中 accountId 是外键且已定义 account 关系字段,推荐更简洁的 关系嵌套写法(无需显式传 ID):
// 方式 1:从 transaction 侧创建,并内联 account(适合先有 account 数据) await prisma.transaction.create({   data: {     type: 'OPENING_BALANCE',     amount: Number(balance),     reference: uuidv4(),     description: `...`,     status: 'ACTIVE',     branchId,     createdById: req.user.id,     account: {       create: {         accountCode: Number(accountCode),         name: name?.trim() ?? '',         type,         description: description ?? '',         balance: Number(balance),         status,         branchId,         createdById: req.user.id,       },     },   }, });  // 方式 2:从 account 侧创建,并关联 transaction(适合主实体明确) await prisma.account.create({   data: {     accountCode: Number(accountCode),     name: name?.trim() ?? '',     type,     description: description ?? '',     balance: Number(balance),     status,     branchId,     createdById: req.user.id,     transactions: {       create: Number(balance) > 0         ? {             type: 'OPENING_BALANCE',             amount: Number(balance),             reference: uuidv4(),             description: `...`,             status: 'ACTIVE',             branchId,             createdById: req.user.id,           }         : undefined,     },   }, });

? 总结:

  • 优先选用 $transaction(async (tx) => {…}) 回调模式处理依赖 ID 的多步操作;
  • 避免在数组式事务中尝试“跨 Promise 引用”,那是反模式;
  • 关系嵌套(create/createMany/connect)在单操作可满足需求时更简洁、声明式更强;
  • 所有方案均需确保 Prisma Schema 中已正确定义 account 与 transaction 的关系(如 accountId int @map(“account_id”) + account Account @relation(fields: [accountId], references: [id]))。

text=ZqhQzanResources