C# ViewModel表单验证失败时创建数据库记录遇Null错误
表单验证失败引发NullReferenceException的修复方案
问题概述
表单验证失败时触发NullReferenceException空引用错误,视图初始加载正常,但验证失败返回视图时程序无法找到所需数据,同时数据保存的实现方式存在冗余和潜在风险。
错误原因分析
- 空引用错误根源:Post动作中验证失败后,虽然创建了新的
ItemAndItemCategoriesViewModel并赋值了ItemCategories,但实际返回的是表单提交过来的viewModel——这个对象的ItemCategories是null(表单不会回传该集合),导致视图遍历Model.ItemCategories时触发空引用。 - 保存逻辑问题:手动复制所有属性冗余且容易出错;直接绑定
ItemCategory对象而非外键,易导致绑定异常;未检查分类ID的有效性,可能引发数据库操作错误。
修复方案
1. 修复空引用错误
修改Post动作,验证失败时直接给传入的viewModel赋值ItemCategories,而非创建新对象:
[HttpPost] [ValidateAntiForgeryToken] public IActionResult Create(ItemAndItemCategoriesViewModel viewModel) { if (ModelState.IsValid) { // 后续保存逻辑(见下文优化) } // 关键:将分类集合赋值给当前viewModel,而非新建对象 viewModel.ItemCategories = _itemCategoriesService.getAllCategories(); return View(viewModel); }
2. 优化数据保存逻辑
步骤1:为Item模型添加显式外键
在Item类中添加外键字段,让EF Core更清晰地处理导航属性,同时简化表单绑定:
[Index(nameof(ItemCode), IsUnique = true)] public class Item { [Key] public int Id { get; set; } public string ItemCode { get; set; } public string ItemDescription { get; set; } public double Cost { get; set; } public double SalePrice { get; set; } public double Micrones { get; set; } public double Density { get; set; } public DateTime CreatedAt { get; set; } = DateTime.Now; // 新增外键字段,用于表单绑定 [DisplayName("Item Category")] public int ItemCategoryId { get; set; } // 导航属性,通过外键关联 [ForeignKey(nameof(ItemCategoryId))] public ItemCategory ItemCategory { get; set; } }
步骤2:修改视图中的下拉框绑定
将下拉框绑定到ItemCategoryId而非整个ItemCategory对象:
<div class="mb-3"> <label asp-for="Item.ItemCategoryId"></label> <select class="form-control" asp-for="Item.ItemCategoryId"> @foreach (var category in Model.ItemCategories) { <option value="@category.Id">@category.CategoryName</option> } </select> <span asp-validation-for="Item.ItemCategoryId" class="text-danger"></span> </div>
步骤3:简化Post动作的保存逻辑
去掉冗余的属性复制,添加分类有效性检查:
[HttpPost] [ValidateAntiForgeryToken] public IActionResult Create(ItemAndItemCategoriesViewModel viewModel) { if (ModelState.IsValid) { // 检查选中的分类是否存在,避免无效ID if (!_itemCategoriesService.Exists(viewModel.Item.ItemCategoryId)) { ModelState.AddModelError(nameof(viewModel.Item.ItemCategoryId), "Invalid category selected"); viewModel.ItemCategories = _itemCategoriesService.getAllCategories(); return View(viewModel); } // 直接使用viewModel中的Item对象,无需手动复制属性 viewModel.Item.CreatedAt = DateTime.Now; // 确保时间正确(也可通过EF配置自动生成) _db.Items.Add(viewModel.Item); _db.SaveChanges(); TempData["success"] = "Item created successfully"; return RedirectToAction("Index"); } viewModel.ItemCategories = _itemCategoriesService.getAllCategories(); return View(viewModel); }
注:需在_itemCategoriesService中添加Exists(int id)方法,用于检查分类是否存在。
修复效果
- 验证失败返回视图时,
ItemCategories会被正确赋值,不再触发空引用错误; - 表单绑定更简洁,避免复杂对象绑定的潜在问题;
- 保存逻辑更简洁,减少冗余代码,同时增加了有效性检查,提升稳定性。
内容的提问来源于stack exchange,提问作者Ela Buwa
相关产品推荐
相关产品推荐

