You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 15:14:56