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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 20:45:11