EF Core DbSet.Update方法新增数据而非更新的问题求助
使用.NET Core 6和EF Core 6编辑Kitap实体实例时,系统未更新原有数据而是新增了一条新实例。尝试在Edit视图中添加@Html.HiddenFor(m => m.kitapId)后,出现如下跟踪冲突错误:
System.InvalidOperationException: 'The instance of entity type 'Kitap' cannot be tracked because another instance with the same key value for {'kitapId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'
要求不使用SQL语句,且在不改变项目结构的前提下解决问题。
相关代码
控制器代码
//GET public IActionResult Edit(int? id) { if (id == null || id == 0) { return NotFound(); } var dbKitap = _db.Kitaplar.Find(id); if (dbKitap == null) { return NotFound(); } return View(dbKitap); } //POST [HttpPost] [ValidateAntiForgeryToken] public IActionResult Edit(Kitap obj) { foreach (var kitap in _db.Kitaplar) { if (kitap.kitapAdi.Equals(obj.kitapAdi)) { ModelState.AddModelError("kitapAdi", "Düzenlemek istediğiniz kitap zaten mevcuttur."); } } if (ModelState.IsValid) { _db.Kitaplar.Update(obj); _db.SaveChanges(); return RedirectToAction("Index"); } return View(obj); }
Edit视图代码
@model Kitap <form method="post" asp-action="Edit"> <div class="border p-3 mt-4"> <h2 class="text-primary">Düzenle</h2> <hr /> </div> <div class="mb-3"> <label asp-for="kitapAdi"></label> <input asp-for="kitapAdi" class="form-control" /> <span asp-validation-for="kitapAdi" class="text-danger"></span> </div> <div class="mb-3"> <label asp-for="kitapSirasi"></label> <input asp-for="kitapSirasi" class="form-control" /> <span asp-validation-for="kitapSirasi" class="text-danger"></span> </div> <button type="submit" class="btn btn-primary" style="width:150px">Düzenle</button> <a asp-controller="Kitap" asp-action="Index" class="btn btn-secondary" style="width:150px"> Listeye Dön </a> </form>
列表页编辑按钮代码
<div class="w-75 btn-group" role="group"> <a asp-controller="Kitap" asp-action="Edit" asp-route-id="@obj.kitapId" class="btn btn-primary mx-2"> <i class="bi bi-pencil-square"></i>Düzenle</a> </div>
模型代码
using System.ComponentModel.DataAnnotations; namespace Kitap_Web.Models { public class Kitap { [Key] public int kitapId { get; set; } [Required(ErrorMessage = "Gerekli alan.")] public string kitapAdi { get; set; } public int kitapSirasi { get; set; } } }
解决方案
1. 补充视图的主键传递
在Edit视图的<form>标签内添加主键隐藏字段,确保POST请求能携带kitapId——否则EF无法识别要更新的实体,会默认执行新增操作:
@model Kitap <form method="post" asp-action="Edit"> @Html.HiddenFor(m => m.kitapId) <!-- 新增此行 --> <!-- 原有表单内容保持不变 --> </form>
2. 修改POST方法解决跟踪冲突
GET方法中已通过_db.Kitaplar.Find(id)加载并跟踪了实体实例,POST方法中传入的Kitap obj是全新的未跟踪对象,调用Update时会导致同主键的两个实例被上下文跟踪,引发冲突。可采用两种方式解决:
方式一:更新已跟踪的实体属性
从上下文获取已跟踪的实例,直接更新其属性,EF会自动检测变化并保存,无需调用Update:
[HttpPost] [ValidateAntiForgeryToken] public IActionResult Edit(Kitap obj) { // 优化重复检查:只查询除当前编辑对象外的同名书籍 var existingKitap = _db.Kitaplar.FirstOrDefault(k => k.kitapAdi.Equals(obj.kitapAdi) && k.kitapId != obj.kitapId); if (existingKitap != null) { ModelState.AddModelError("kitapAdi", "Düzenlemek istediğiniz kitap zaten mevcuttur."); } if (ModelState.IsValid) { var dbKitap = _db.Kitaplar.Find(obj.kitapId); if (dbKitap != null) { // 更新属性 dbKitap.kitapAdi = obj.kitapAdi; dbKitap.kitapSirasi = obj.kitapSirasi; // EF自动跟踪变化,直接保存 _db.SaveChanges(); } return RedirectToAction("Index"); } return View(obj); }
方式二:分离原有跟踪实例再更新
先从上下文的本地跟踪集合中找到对应实例并分离,再附加新对象执行更新:
[HttpPost] [ValidateAntiForgeryToken] public IActionResult Edit(Kitap obj) { var existingKitap = _db.Kitaplar.FirstOrDefault(k => k.kitapAdi.Equals(obj.kitapAdi) && k.kitapId != obj.kitapId); if (existingKitap != null) { ModelState.AddModelError("kitapAdi", "Düzenlemek istediğiniz kitap zaten mevcuttur."); } if (ModelState.IsValid) { // 分离上下文中原有的跟踪实例 var trackedKitap = _db.Kitaplar.Local.FirstOrDefault(k => k.kitapId == obj.kitapId); if (trackedKitap != null) { _db.Entry(trackedKitap).State = EntityState.Detached; } // 现在可以安全更新新对象 _db.Kitaplar.Update(obj); _db.SaveChanges(); return RedirectToAction("Index"); } return View(obj); }
3. 优化重复检查逻辑
原代码遍历所有Kitaplar数据效率极低,改用FirstOrDefault查询仅需一次数据库请求,且排除当前编辑的实体(避免自己和自己重复)。
内容的提问来源于stack exchange,提问作者wiedersehen

