如何将HTML表格多行数据存入数据库?解决描述列覆盖问题
批量保存表格记录时Description字段被覆盖的解决方案
问题场景
在将HTML表格中的成员考勤记录批量保存到数据库时,所有记录的Description字段都被第一条输入的值覆盖,其他字段(如MemberId)能正常对应到每一行,但描述无法实现行级独立存储。
原代码问题分析
当前实现的核心问题在于:
- ViewModel中
Description是单个字符串属性,无法存储多行独立的描述内容; - 页面中所有行的Description输入框绑定到同一个模型属性,表单提交时只有最后一个(或第一个)输入值会被绑定,导致循环保存时所有记录复用同一个值。
原ViewModel代码
public SelectList Meetings { get; set; } public string MeetingId { get; set; } public string Description { get; set; } public bool IsPresent { get; set; } public DateTime Date { get; set; } public List<SelectListItem> Members { get; set; } public string MemberId { get; set; }
原View代码(核心部分)
<tbody> @for (int i = 0; i < Model.Members.Count(); i++) { <tr> <td>@Model.Members[i].Text</td> <td><input asp-for="@Model.Description" id="desc"/></td> <td> <input type="hidden" class="custom-checkbox" asp-for="@Model.Members[i].Value" /> <label class="a8z-toggle-switch" data-size="sm" data-color="blue" data-text="true" data-style="rounded"> <input asp-for="Members[i].Selected" type="checkbox" id="example_default_1"> <span class="toggle"> <span class="switch"></span> </span> </label> </td> </tr> } </tbody>
原Controller代码(核心部分)
[HttpPost] public IActionResult AddAttendance(AddAttendanceVM addAttendanceVM) { foreach (var att in addAttendanceVM.Members) { Attendance addThisAttendance = new() { Date = addAttendanceVM.Date, Description = addAttendanceVM.Description, MemberId = att.Value, MeetingId = addAttendanceVM.MeetingId, IsPresent = addAttendanceVM.IsPresent, CreatedBy = User.Claims.FirstOrDefault(c => c.Type == "Name").Value, CreatedDate = DateTime.Now }; xct.Attendances.Add(addThisAttendance); } xct.SaveChanges(); return ViewComponent(nameof(AttendanceLists)); }
解决方案
通过拆分模型为主ViewModel+子模型的结构,实现行级数据的独立绑定与存储。
1. 重构ViewModel
新增子模型存储单条成员考勤数据,主ViewModel用列表承载所有子模型:
// 子模型:存储单条成员的考勤信息 public class MemberAttendanceItem { public string MemberId { get; set; } public string MemberName { get; set; } public string Description { get; set; } public bool IsPresent { get; set; } } // 主ViewModel:存储全局数据与所有成员考勤列表 public class AddAttendanceVM { public SelectList Meetings { get; set; } public string MeetingId { get; set; } public DateTime Date { get; set; } public List<MemberAttendanceItem> MemberAttendances { get; set; } = new List<MemberAttendanceItem>(); }
2. 更新View页面
遍历子模型列表,使用带索引的绑定方式(asp-for="@Model.MemberAttendances[i].Description")确保每个输入框的name属性唯一,实现行级数据独立提交:
@model AddAttendanceVM @using (Html.BeginForm("AddAttendance","Attendance",FormMethod.Post)) { <div class="card-body"> <div class="form-group"> <input type="date" asp-for="@Model.Date" class="form-control js-datepicker" asp-format="{0:dd/MMM/yyyy}"> </div> <div class="form-group"> <select type="text" id="meetingType" class="select2 form-control" asp-items="@Model.Meetings" asp-for="@Model.MeetingId" style="width:100%" multiple></select> </div> <div class="table-responsive"> <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> <thead> <tr> <th>Name</th> <th>Description</th> <th>Action</th> </tr> </thead> <tbody> @for (int i = 0; i < Model.MemberAttendances.Count(); i++) { <tr> <td>@Model.MemberAttendances[i].MemberName</td> <td><input asp-for="@Model.MemberAttendances[i].Description" class="form-control"/></td> <td> <input type="hidden" asp-for="@Model.MemberAttendances[i].MemberId"/> <label class="a8z-toggle-switch" data-size="sm" data-color="blue" data-text="true" data-style="rounded"> <input asp-for="@Model.MemberAttendances[i].IsPresent" type="checkbox"> <span class="toggle"> <span class="switch"></span> </span> </label> </td> </tr> } </tbody> </table> </div> <button type="submit" class="btn btn-primary">保存考勤</button> </div> }
3. 调整Controller逻辑
在POST方法中遍历子模型列表,将每个子模型的数据映射到数据库实体,实现每条记录的独立保存:
[HttpPost] public IActionResult AddAttendance(AddAttendanceVM addAttendanceVM) { if (!ModelState.IsValid) { // 验证失败时重新加载页面数据(需补充会议选项与成员数据的加载逻辑) // addAttendanceVM.Meetings = new SelectList(...); return View(addAttendanceVM); } var createdBy = User.Claims.FirstOrDefault(c => c.Type == "Name")?.Value; foreach (var item in addAttendanceVM.MemberAttendances) { // 可根据业务需求过滤需要保存的记录(比如仅保存已勾选的考勤) if (item.IsPresent) { var attendance = new Attendance { Date = addAttendanceVM.Date, Description = item.Description, MemberId = item.MemberId, MeetingId = addAttendanceVM.MeetingId, IsPresent = item.IsPresent, CreatedBy = createdBy, CreatedDate = DateTime.Now }; xct.Attendances.Add(attendance); } } xct.SaveChanges(); return ViewComponent(nameof(AttendanceLists)); }
补充:GET方法数据初始化
在返回页面的GET方法中,需要将成员数据填充到MemberAttendances列表:
public IActionResult AddAttendance() { var vm = new AddAttendanceVM(); // 加载会议下拉选项 vm.Meetings = new SelectList(/* 会议数据源, "Value字段", "Text字段" */); // 加载成员数据并填充到子模型列表 var members = /* 从数据库获取成员列表 */; vm.MemberAttendances = members.Select(m => new MemberAttendanceItem { MemberId = m.Id, MemberName = m.Name, IsPresent = false // 默认未勾选 }).ToList(); return View(vm); }
内容的提问来源于stack exchange,提问作者Katakyie Kofi Poku
相关产品推荐
相关产品推荐

