在.NET Core书店项目中实现添加新书时读取展示作者的问题
嘿,我看你正在搭建.NET Core的虚构书店项目,要实现添加新书时让用户选择现有作者的功能对吧?这是个典型的多对多关联场景,我给你梳理一套完整的实现方案:
步骤1:确保EF Core多对多关联配置正确
首先得确保你的DbContext和实体类正确配置了Books、Authors以及中间表BookAuthors的多对多关系,这样EF才能正确处理关联数据:
// DbContext配置 public class BookStoreDbContext : DbContext { public BookStoreDbContext(DbContextOptions<BookStoreDbContext> options) : base(options) { } public DbSet<Book> Books { get; set; } public DbSet<Author> Authors { get; set; } public DbSet<BookAuthor> BookAuthors { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置中间表的复合主键 modelBuilder.Entity<BookAuthor>() .HasKey(ba => new { ba.BookId, ba.AuthorId }); // 建立Book与BookAuthor的关联 modelBuilder.Entity<BookAuthor>() .HasOne(ba => ba.Book) .WithMany(b => b.BookAuthors) .HasForeignKey(ba => ba.BookId); // 建立Author与BookAuthor的关联 modelBuilder.Entity<BookAuthor>() .HasOne(ba => ba.Author) .WithMany(a => a.BookAuthors) .HasForeignKey(ba => ba.AuthorId); } } // Book实体类 public class Book { public int Id { get; set; } public string Title { get; set; } public decimal Price { get; set; } // 其他书籍属性(比如出版日期、ISBN等) public ICollection<BookAuthor> BookAuthors { get; set; } = new List<BookAuthor>(); } // Author实体类 public class Author { public int Id { get; set; } public string Name { get; set; } // 其他作者属性(比如出生日期、简介等) public ICollection<BookAuthor> BookAuthors { get; set; } = new List<BookAuthor>(); } // 中间表实体类 public class BookAuthor { public int BookId { get; set; } public Book Book { get; set; } public int AuthorId { get; set; } public Author Author { get; set; } }
步骤2:创建用于视图的ViewModel
因为添加页面需要同时处理新书信息和作者选择列表,直接用实体类不太方便,所以创建一个ViewModel来封装这些数据:
public class BookCreateViewModel { // 新书的基本属性,和Book实体对应 public string Title { get; set; } public decimal Price { get; set; } // 按需添加其他书籍字段... // 存储用户选中的作者ID数组(多对多支持多选) public int[] SelectedAuthorIds { get; set; } // 供视图渲染的作者选项列表 public SelectList AuthorOptions { get; set; } }
步骤3:完善BookController的Add方法
接下来修改你的BookController,在GET方法中加载所有作者并填充ViewModel,POST方法中处理表单提交并保存关联数据:
public class BookController : Controller { private readonly BookStoreDbContext _context; // 通过依赖注入获取DbContext实例 public BookController(BookStoreDbContext context) { _context = context; } // GET: Book/Add public async Task<IActionResult> Add() { // 从数据库获取所有作者,按姓名排序提升用户体验 var authors = await _context.Authors.OrderBy(a => a.Name).ToListAsync(); // 初始化ViewModel并填充作者选项 var viewModel = new BookCreateViewModel { AuthorOptions = new SelectList(authors, "Id", "Name") }; return View(viewModel); } // POST: Book/Add [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Add(BookCreateViewModel viewModel) { if (ModelState.IsValid) { // 从ViewModel创建Book实体 var newBook = new Book { Title = viewModel.Title, Price = viewModel.Price, // 映射其他书籍属性... }; // 先保存新书,获取自动生成的BookId _context.Books.Add(newBook); await _context.SaveChangesAsync(); // 如果用户选中了作者,批量添加到中间表 if (viewModel.SelectedAuthorIds != null && viewModel.SelectedAuthorIds.Any()) { var bookAuthorRelations = viewModel.SelectedAuthorIds .Select(authorId => new BookAuthor { BookId = newBook.Id, AuthorId = authorId }); _context.BookAuthors.AddRange(bookAuthorRelations); await _context.SaveChangesAsync(); } // 保存成功后跳转到书籍列表页 return RedirectToAction(nameof(Index)); } // 如果模型验证失败,重新加载作者选项并返回视图 var authors = await _context.Authors.OrderBy(a => a.Name).ToListAsync(); viewModel.AuthorOptions = new SelectList(authors, "Id", "Name"); return View(viewModel); } }
步骤4:编写Add视图
最后编写Add.cshtml视图,用复选框让用户选择多个作者(多对多场景下复选框比下拉框更友好):
@model BookCreateViewModel @{ ViewData["Title"] = "Add New Book"; } <h1 class="mb-4">Add New Book</h1> <form asp-action="Add"> <div asp-validation-summary="ModelOnly" class="text-danger mb-3"></div> <!-- 书籍标题输入框 --> <div class="form-group mb-3"> <label asp-for="Title" class="form-label"></label> <input asp-for="Title" class="form-control" /> <span asp-validation-for="Title" class="text-danger"></span> </div> <!-- 书籍价格输入框 --> <div class="form-group mb-3"> <label asp-for="Price" class="form-label"></label> <input asp-for="Price" class="form-control" /> <span asp-validation-for="Price" class="text-danger"></span> </div> <!-- 作者选择区域 --> <div class="form-group mb-3"> <label class="form-label">Select Authors</label> <div class="row"> @foreach (var authorOption in Model.AuthorOptions) { <div class="col-md-4 mb-2"> <div class="form-check"> <input class="form-check-input" type="checkbox" name="SelectedAuthorIds" value="@authorOption.Value" @(Model.SelectedAuthorIds != null && Model.SelectedAuthorIds.Contains(int.Parse(authorOption.Value)) ? "checked" : "") /> <label class="form-check-label">@authorOption.Text</label> </div> </div> } </div> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">Save Book</button> <a asp-action="Index" class="btn btn-secondary ms-2">Back to List</a> </div> </form> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
额外注意点
- 如果使用的是EF Core 5及以上版本,你可以省略显式的BookAuthor中间实体,直接在Book类中定义
public ICollection<Author> Authors { get; set; },在Author类中定义public ICollection<Book> Books { get; set; },EF会自动创建中间表。但显式定义中间实体的好处是,后续如果需要给中间表添加额外字段(比如作者的合作顺序),扩展起来更方便。 - 确保在Program.cs(.NET 6+)或Startup.cs中正确注册DbContext,依赖注入才能正常工作:
// .NET 6+ Program.cs示例 builder.Services.AddDbContext<BookStoreDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("BookStoreConnection"))); - 视图中的复选框
name属性必须和ViewModel的SelectedAuthorIds字段名一致,这样ASP.NET Core的模型绑定才能正确接收用户选中的作者ID数组。
内容的提问来源于stack exchange,提问作者Kaloyan Drenski
相关产品推荐
相关产品推荐

