如何在ASP.Net MVC 5中遍历MultiSelectList提交值并逐条插入数据库行?
解决ASP.NET MVC5中MultiSelectList多对多关联插入问题
我之前在ASP.NET MVC5里做过类似的DVD商店多对多关联功能,刚好踩过你遇到的这个坑,给你梳理下解决的关键步骤和代码示例:
1. 先确认实体模型与EF配置
因为你提到中间多对多表有自己的主键,所以需要用显式的中间实体(而不是EF自动生成的隐式关联表),先定义好三个实体类:
// 电影实体 public class Movie { public int Id { get; set; } public string Title { get; set; } public decimal Cost { get; set; } public string ImagePath { get; set; } public string Director { get; set; } public string Rating { get; set; } // 关联中间表集合 public ICollection<MovieGenre> MovieGenres { get; set; } = new List<MovieGenre>(); } // 流派实体 public class Genre { public int Id { get; set; } public string Name { get; set; } // 关联中间表集合 public ICollection<MovieGenre> MovieGenres { get; set; } = new List<MovieGenre>(); } // 显式多对多中间表(带主键) public class MovieGenre { public int Id { get; set; } // 你提到的主键 public int MovieId { get; set; } public int GenreId { get; set; } // 导航属性 public Movie Movie { get; set; } public Genre Genre { get; set; } }
然后在DbContext里配置实体关系,确保EF能正确识别关联:
public class DVDStoreDbContext : DbContext { public DbSet<Movie> Movies { get; set; } public DbSet<Genre> Genres { get; set; } public DbSet<MovieGenre> MovieGenres { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { // 配置中间表主键 modelBuilder.Entity<MovieGenre>() .HasKey(mg => mg.Id); // 配置Movie与MovieGenre的一对多关系 modelBuilder.Entity<MovieGenre>() .HasRequired(mg => mg.Movie) .WithMany(m => m.MovieGenres) .HasForeignKey(mg => mg.MovieId) .WillCascadeOnDelete(true); // 配置Genre与MovieGenre的一对多关系 modelBuilder.Entity<MovieGenre>() .HasRequired(mg => mg.Genre) .WithMany(g => g.MovieGenres) .HasForeignKey(mg => mg.GenreId) .WillCascadeOnDelete(true); } }
2. 构建ViewModel用于视图绑定
视图里的MultiSelectList需要借助ViewModel来传递数据和接收选中的流派ID,定义如下:
public class MovieCreateViewModel { // 电影基本属性,和Movie实体对应 public string Title { get; set; } [DataType(DataType.Currency)] public decimal Cost { get; set; } public string ImagePath { get; set; } public string Director { get; set; } public string Rating { get; set; } // 存储用户选中的流派ID数组(关键!) public int[] SelectedGenreIds { get; set; } // 用于生成多选下拉列表的数据源 public MultiSelectList Genres { get; set; } }
3. 控制器的Create方法逻辑
重点是先插入电影并保存拿到有效ID,再批量插入中间表记录,因为中间表需要依赖新电影的Id:
[HttpGet] public ActionResult Create() { var viewModel = new MovieCreateViewModel(); // 加载所有流派到MultiSelectList viewModel.Genres = new MultiSelectList(db.Genres, "Id", "Name"); return View(viewModel); } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(MovieCreateViewModel viewModel) { if (!ModelState.IsValid) { // 验证失败时重新加载流派列表 viewModel.Genres = new MultiSelectList(db.Genres, "Id", "Name"); return View(viewModel); } // 1. 先创建电影实体并插入数据库 var newMovie = new Movie { Title = viewModel.Title, Cost = viewModel.Cost, ImagePath = viewModel.ImagePath, Director = viewModel.Director, Rating = viewModel.Rating }; db.Movies.Add(newMovie); db.SaveChanges(); // 必须先SaveChanges,才能拿到newMovie.Id // 2. 处理选中的流派,批量插入中间表 if (viewModel.SelectedGenreIds != null && viewModel.SelectedGenreIds.Any()) { foreach (var genreId in viewModel.SelectedGenreIds) { var movieGenre = new MovieGenre { MovieId = newMovie.Id, GenreId = genreId }; db.MovieGenres.Add(movieGenre); } db.SaveChanges(); // 批量保存中间表记录 } return RedirectToAction("Index"); }
4. 视图中的多选列表代码
确保ListBoxFor的名称和ViewModel的SelectedGenreIds对应,这样表单提交时才能正确绑定:
@model DVDStore.Models.MovieCreateViewModel @{ ViewBag.Title = "添加新电影"; } <h2>添加新电影</h2> @using (Html.BeginForm("Create", "Movies", FormMethod.Post, new { enctype = "multipart/form-data" })) { @Html.AntiForgeryToken() <div class="form-horizontal"> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <!-- 电影基本属性的表单字段省略,和你现有逻辑一致 --> <!-- 流派多选列表 --> <div class="form-group"> @Html.LabelFor(m => m.SelectedGenreIds, "所属流派", htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.ListBoxFor(m => m.SelectedGenreIds, Model.Genres, new { @class = "form-control", size = 5 }) @Html.ValidationMessageFor(m => m.SelectedGenreIds, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="保存" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("返回列表", "Index") </div>
常见坑点提醒
- 必须先SaveChanges获取Movie的Id:如果直接创建MovieGenre再一起保存,EF可能无法自动生成Movie的Id,导致中间表的MovieId为0
- 处理SelectedGenreIds为null的情况:如果用户没选任何流派,这个数组会是null,所以要加非空判断
- 确保ViewModel的属性名和表单控件name一致:ListBoxFor会自动生成name="SelectedGenreIds",和ViewModel的属性对应,不要手动修改name
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

