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
相关产品推荐
相关产品推荐

