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

ASP.NET Razor Pages中M:N表添加食谱与配料及数量的实现问题

解决方案:ASP.NET Razor Pages 食谱项目多对多关系与配料添加

核心问题解答

  • 是否需要直接向中间表RecipeIngredient添加ID?
    不需要直接操作中间表,EF Core可通过实体导航属性自动管理关联,但因需添加quantity字段,必须显式定义中间实体,不能再使用EF的隐式多对多配置。
  • 是否需将quantity列加入中间表?
    是的,多对多关系中的额外属性(如配料数量)必须存储在中间关联表中,因此需要显式创建包含该字段的中间实体。

步骤1:显式创建中间关联实体

创建RecipeIngredientEntity类,用于存储食谱与配料的关联及数量:

public class RecipeIngredientEntity
{
    public int RecipeId { get; set; }
    public RecipeEntity Recipe { get; set; }
    
    public int IngredientId { get; set; }
    public IngredientEntitty Ingredient { get; set; }
    
    public decimal Quantity { get; set; }
    public string Unit { get; set; } // 可选:添加单位字段,比如"克"、"杯"
}

步骤2:修改RecipeEntity和IngredientEntity

更新两个实体的导航属性,指向中间关联实体:

// RecipeEntity修改后
public class RecipeEntity
{
    public int Id { get; set; } 
    public string NameOfRecipe { get; set; }
    public string Author { get; set; } = "Unknown";
    public string Directions { get; set; }
    // 替换原Ingredients导航属性
    public ICollection<RecipeIngredientEntity> RecipeIngredients { get; set; }
}

// IngredientEntity修改后
public class IngredientEntitty
{
    public int Id { get; set; }
    public string NameOfIngredient { get; set; }
    // 替换原Recipes导航属性
    public ICollection<RecipeIngredientEntity> RecipeIngredients { get; set; }
}

步骤3:更新ApplicationDbContext配置

在OnModelCreating中配置三个实体的关系,指定中间表的复合主键:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 
        : base(options)
    {}

    public DbSet<RecipeEntity> Recipes { get; set; }
    public DbSet<IngredientEntitty> Ingredients { get; set; }
    // 添加中间实体的DbSet
    public DbSet<RecipeIngredientEntity> RecipeIngredients { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 配置中间表的复合主键
        modelBuilder.Entity<RecipeIngredientEntity>()
            .HasKey(ri => new { ri.RecipeId, ri.IngredientId });

        // 配置Recipe与中间表的关系
        modelBuilder.Entity<RecipeIngredientEntity>()
            .HasOne(ri => ri.Recipe)
            .WithMany(r => r.RecipeIngredients)
            .HasForeignKey(ri => ri.RecipeId);

        // 配置Ingredient与中间表的关系
        modelBuilder.Entity<RecipeIngredientEntity>()
            .HasOne(ri => ri.Ingredient)
            .WithMany(i => i.RecipeIngredients)
            .HasForeignKey(ri => ri.IngredientId);
    }
}

步骤4:修改AddNewRecipeModel处理配料提交

创建ViewModel类接收用户选择的配料和数量,再更新页面模型逻辑:

// 新增ViewModel
public class RecipeIngredientViewModel
{
    public int IngredientId { get; set; }
    public decimal Quantity { get; set; }
    public string Unit { get; set; } // 可选
}

// 修改AddNewRecipeModel
[BindProperties(SupportsGet = true)]
public class AddNewRecipeModel : PageModel
{
    private readonly BookOfRecipes.Data.ApplicationDbContext _context;

    public AddNewRecipeModel(BookOfRecipes.Data.ApplicationDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public RecipeEntity Recipe { get; set; }

    // 绑定用户提交的配料列表
    [BindProperty]
    public List<RecipeIngredientViewModel> SelectedIngredients { get; set; }

    public List<IngredientEntitty> ListOfIngredients { get; set; }

    public void OnGet()
    {
        ListOfIngredients = _context.Ingredients.ToList();
        // 初始化列表避免空引用
        SelectedIngredients = new List<RecipeIngredientViewModel>();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            ListOfIngredients = _context.Ingredients.ToList();
            return Page();
        }

        // 添加食谱到数据库
        _context.Recipes.Add(Recipe);
        await _context.SaveChangesAsync();

        // 创建并添加关联的配料记录
        foreach (var item in SelectedIngredients)
        {
            if (item.IngredientId > 0 && item.Quantity > 0)
            {
                var recipeIngredient = new RecipeIngredientEntity
                {
                    RecipeId = Recipe.Id,
                    IngredientId = item.IngredientId,
                    Quantity = item.Quantity,
                    Unit = item.Unit
                };
                _context.RecipeIngredients.Add(recipeIngredient);
            }
        }

        await _context.SaveChangesAsync();
        return RedirectToPage("/Recipes/Index"); // 跳转到食谱列表页
    }
}

步骤5:修改前端视图AddNewRecipe.cshtml

更新表单,支持选择多个配料并输入数量(示例为动态添加行方式):

@page
@model BookOfRecipes.Pages.NewRecipe.AddNewRecipeModel

<form method="post">
    <div>
        <label asp-for="Recipe.NameOfRecipe">食谱名称</label>
        <input asp-for="Recipe.NameOfRecipe" />
        <span asp-validation-for="Recipe.NameOfRecipe" class="text-danger"></span>
    </div>
    <div>
        <label asp-for="Recipe.Author">作者</label>
        <input asp-for="Recipe.Author" />
        <span asp-validation-for="Recipe.Author" class="text-danger"></span>
    </div>
    <div>
        <label asp-for="Recipe.Directions">制作步骤</label>
        <textarea asp-for="Recipe.Directions"></textarea>
        <span asp-validation-for="Recipe.Directions" class="text-danger"></span>
    </div>

    <h4>配料</h4>
    <div id="ingredients-container">
        @foreach (var (item, index) in Model.SelectedIngredients.Select((x, i) => (x, i)))
        {
            <div class="ingredient-row">
                <select asp-for="SelectedIngredients[@index].IngredientId" asp-items="@(new SelectList(Model.ListOfIngredients, "Id", "NameOfIngredient"))">
                    <option value="">选择配料</option>
                </select>
                <input asp-for="SelectedIngredients[@index].Quantity" type="number" step="0.1" placeholder="数量" />
                <input asp-for="SelectedIngredients[@index].Unit" placeholder="单位(如克)" />
                <button type="button" onclick="removeRow(this)">删除</button>
            </div>
        }
    </div>
    <button type="button" onclick="addIngredientRow()">添加配料</button>

    <input type="submit" value="保存食谱" />
</form>

<script>
    function addIngredientRow() {
        const container = document.getElementById('ingredients-container');
        const rowCount = container.children.length;
        const newRow = document.createElement('div');
        newRow.className = 'ingredient-row';
        newRow.innerHTML = `
            <select name="SelectedIngredients[${rowCount}].IngredientId">
                <option value="">选择配料</option>
                @foreach (var ingredient in Model.ListOfIngredients)
                {
                    <option value="@ingredient.Id">@ingredient.NameOfIngredient</option>
                }
            </select>
            <input name="SelectedIngredients[${rowCount}].Quantity" type="number" step="0.1" placeholder="数量" />
            <input name="SelectedIngredients[${rowCount}].Unit" placeholder="单位(如克)" />
            <button type="button" onclick="removeRow(this)">删除</button>
        `;
        container.appendChild(newRow);
    }

    function removeRow(button) {
        button.parentElement.remove();
    }
</script>

最后说明

  • 执行Add-Migration AddQuantityToRecipeIngredient和Update-Database命令,更新数据库结构。
  • 可根据需求添加模型验证(如[Required]、[Range]),确保提交数据合法。
  • 前端动态行逻辑可根据样式需求调整。

内容的提问来源于stack exchange,提问作者MultiSteelking

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 14:45:05