NestJS+Prisma中Recipe关联Ingredient并添加amount字段(无需新表)
解决方案:基于Prisma显式多对多关联实现需求
你需要用显式多对多关系来达成目标——因为要在关联Recipe和Ingredient时存储amount字段,这属于带额外属性的多对多关联,Prisma原生支持这种场景,对应的中间表是关联逻辑必需的,不属于额外业务表。
1. 修改Prisma Schema
更新你的schema,添加Recipe模型和带amount字段的显式多对多关联表:
model Ingredient { id String @id @unique @default(cuid()) name String @unique // 关联到中间表 recipeIngredients RecipeIngredient[] } model Recipe { id String @id @unique @default(cuid()) title String description String? // 关联到中间表 recipeIngredients RecipeIngredient[] } // 显式多对多中间表,存储食材用量 model RecipeIngredient { id String @id @unique @default(cuid()) recipeId String ingredientId String amount String // 你的用量字段 // 外键关联配置 recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade) ingredient Ingredient @relation(fields: [ingredientId], references: [id], onDelete: Cascade) // 复合唯一键,避免同一食材重复关联到同一食谱 @@unique([recipeId, ingredientId]) }
2. 更新Prisma客户端
运行命令同步数据库并生成新的客户端代码:
npx prisma generate npx prisma migrate dev
3. 定义NestJS的DTO
创建匹配你期望JSON结构的请求/响应DTO:
// recipe.dto.ts export class IngredientWithAmountDto { id: string; name: string; amount: string; } export class CreateRecipeDto { title: string; description?: string; ingredients: { id: string; amount: string }[]; // 请求仅需食材ID和用量 } export class RecipeResponseDto { id: string; title: string; description?: string; ingredients: IngredientWithAmountDto[]; }
4. 实现NestJS服务层逻辑
在RecipeService中处理创建和查询逻辑,同时转换数据格式:
// recipe.service.ts import { Injectable } from '@nestjs/common'; import { PrismaService } from './prisma.service'; import { CreateRecipeDto, RecipeResponseDto } from './dto/recipe.dto'; @Injectable() export class RecipeService { constructor(private prisma: PrismaService) {} async createRecipe(dto: CreateRecipeDto): Promise<RecipeResponseDto> { // 创建食谱并关联已有食材 const recipe = await this.prisma.recipe.create({ data: { title: dto.title, description: dto.description, recipeIngredients: { create: dto.ingredients.map(item => ({ ingredientId: item.id, amount: item.amount, })), }, }, include: { recipeIngredients: { include: { ingredient: true } }, }, }); return this.formatRecipeResponse(recipe); } async getRecipeById(id: string): Promise<RecipeResponseDto> { const recipe = await this.prisma.recipe.findUnique({ where: { id }, include: { recipeIngredients: { include: { ingredient: true } }, }, }); return this.formatRecipeResponse(recipe); } // 统一转换数据为期望的JSON格式 private formatRecipeResponse(recipe: any): RecipeResponseDto { return { id: recipe.id, title: recipe.title, description: recipe.description, ingredients: recipe.recipeIngredients.map(ri => ({ id: ri.ingredient.id, name: ri.ingredient.name, amount: ri.amount, })), }; } }
方案优势
- 复用现有Ingredient:创建食谱仅需传入已有食材ID,不会生成新的Ingredient记录
- 无冗余业务表:中间表
RecipeIngredient是多对多关联的标准实现,仅用于存储关联关系和用量 - 完美匹配目标JSON结构:通过服务层的格式转换,直接输出你需要的响应结构
内容的提问来源于stack exchange,提问作者Shrew Fallout
相关产品推荐
相关产品推荐

