如何在Prisma中通过外键更新实体?Express场景问题解决
我开发了一个简单API,允许已认证用户对视频执行CRUD操作。Video模型包含名为creatorId的外键,关联创建该视频的User模型。尝试将creatorId传入Prisma函数的where子句更新视频时,出现如下错误:
Unknown arg `creatorId` in where.creatorId for type VideoWhereUniqueInput. Available args: type VideoWhereUniqueInput { id?: Int }
我的Express JS实现代码
export const updateVideo = async (req, res, next) => { const userId = parseInt(req.user.id) const videoId = parseInt(req.params.id) try { // 检查视频是否存在 const videoToUpdate = await prisma.video.findUnique({ where: { id: videoId } }) // 视频不存在则抛出错误 if (!videoToUpdate) return next(createError(404, `ID为${req.params.id}的视频不存在`)) // 更新视频 const updatedVideo = await prisma.video.update({ // 尝试定位用户创建的视频 where: { creatorId: userId }, // 要修改的数据 data: { ...req.body, } }) res.status(200).json(updatedVideo) } catch (error) { console.log(error) next(error) } }
Prisma模型定义
model User { id Int @id @default(autoincrement()) email String @unique name String password String img String? subscriberCount Int? @default(0) videos Video[] comments Comment[] followedBy Follows[] @relation("following") following Follows[] @relation("follower") likedVideos Likes[] } model Video { id Int @id @default(autoincrement()) creator User @relation(fields: [creatorId], references: [id]) creatorId Int title String description String videoUrl String? views Int? @default(0) tag String? @default("Uncategorized") likedBy Likes[] comments Comment[] }
我想知道如何修复该问题,使Prisma能够基于外键执行更新查询?另外我好奇为何这种简单方案无效,比如为何主键可以这么用而外键不行?
修复方案
问题出在prisma.video.update的where条件上:你需要同时指定id和creatorId来定位唯一且属于当前用户的视频,而且要给Video模型添加id+creatorId的复合唯一约束,这样Prisma才会把这组字段识别为有效的唯一查询条件。
步骤1:修改Prisma模型,添加复合唯一约束
在Video模型中给id和creatorId添加@@unique约束:
model Video { id Int @id @default(autoincrement()) creator User @relation(fields: [creatorId], references: [id]) creatorId Int title String description String videoUrl String? views Int? @default(0) tag String? @default("Uncategorized") likedBy Likes[] comments Comment[] // 添加复合唯一约束 @@unique([id, creatorId]) }
然后运行npx prisma migrate dev应用这个变更。
步骤2:修改updateVideo函数的where条件
现在可以同时用id和creatorId作为查询条件,既确保更新的是指定ID的视频,又验证该视频属于当前用户:
export const updateVideo = async (req, res, next) => { const userId = parseInt(req.user.id) const videoId = parseInt(req.params.id) try { // 直接用复合条件更新,同时验证视频存在且属于当前用户 const updatedVideo = await prisma.video.update({ where: { id_creatorId: { // Prisma自动生成的复合字段名:id+下划线+creatorId id: videoId, creatorId: userId } }, data: { ...req.body, } }) res.status(200).json(updatedVideo) } catch (error) { // 处理视频不存在或无权限的情况 if (error.code === 'P2025') { return next(createError(404, `视频不存在或您无权限修改`)) } console.log(error) next(error) } }
这样修改后,既实现了权限验证(只有视频创作者能修改),又符合Prisma的查询规则。
为什么直接用creatorId不行?
Prisma的update、delete这类操作要求where子句必须传入唯一标识(即VideoWhereUniqueInput类型),目的是确保只会操作一条数据:
- 主键
id天生是唯一的,所以可以单独用来定位一条视频; - 而
creatorId只是外键,一个用户可以创建多个视频,单独用它无法定位到唯一的视频,不符合VideoWhereUniqueInput的要求,因此Prisma不允许这么用。
只有当某个字段(或字段组合)被标记为unique时,Prisma才会把它加入到WhereUniqueInput的可用参数中,这就是为什么添加复合唯一约束后就能用id+creatorId作为查询条件的原因。
内容的提问来源于stack exchange,提问作者kimetsu no yaiba

