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

EF更新WorkshopReport时遇主键冲突错误,如何解决?

解决EF更新WorkshopReport时的主键冲突错误

问题描述

更新WorkshopReport记录时触发以下错误:

错误信息:{"Attaching an entity of type 'DomainClass.WorkshopReport' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.}

相关代码

领域类(Domain Class)

namespace DomainClass { 
    public class WorkshopReport { 
        public int Id { set; get; } 
        public int UserId { set; get; } 
        public int? ManagerIdConfirm { set; get; } 
        public bool? ManagerConfirmState { set; get; } 
        public DateTime? ManagerConfirmDateTime { set; get; } 
        public int? SuperviderIdConfirm { set; get; } 
        public bool? SuperviderConfirmState { set; get; } 
        public DateTime? SuperviderConfirmDateTime { set; get; } 
        public string ReportNumber { set; get; } 
        public string Shift { set; get; } 
        public string ShiftWork { set; get; } 
        public DateTime ShiftDate { set; get; } 
        public string ShiftPersennel { set; get; } 
        public string Type { set; get; } 
        public DateTime SubmitDateTime { set; get; } 
    } 
}

仓储接口(Interface Repository)

namespace InterfaceRepository { 
    public interface IWorkshopReportRepository { 
        IQueryable<WorkshopReport> Get(); 
        bool Save(); 
        bool Add(WorkshopReport newValue); 
        bool Delete(WorkshopReport deleted); 
        bool Edit(WorkshopReport updated); 
        IQueryable<WorkshopReport> FindById(int Id); 
        IQueryable<WorkshopReport> Search(string ReportNumber); 
    } 
}

仓储层(Repository Layer)

namespace RepositoryLayer { 
    public class WorkshopReportRepository:IWorkshopReportRepository { 
        public CMSDataContext _ctx; 
        public WorkshopReportRepository(CMSDataContext ctx) { 
            _ctx = ctx; 
        } 
        public IQueryable<WorkshopReport> Get() { 
            return _ctx.WorkshopReports; 
        } 
        public bool Save() { 
            try { 
                return _ctx.SaveChanges() > 0; 
            } catch (Exception ex) { 
                // TODO log this error 
                return false; 
            } 
        } 
        public bool Add(WorkshopReport newValue) { 
            try { 
                _ctx.WorkshopReports.Add(newValue); 
                return true; 
            } catch (Exception ex) { 
                // TODO log this error 
                return false; 
            } 
        } 
        public bool Delete(WorkshopReport deleted) { 
            try { 
                _ctx.WorkshopReports.Remove(deleted); 
                return true; 
            } catch (Exception ex) { 
                // TODO log this error 
                return false; 
            } 
        } 
        public bool Edit(WorkshopReport updated) { 
            try { 
                _ctx.Entry(updated).State = System.Data.Entity.EntityState.Modified; 
                return true; 
            } catch (Exception ex) { 
                // TODO log this error 
                return false; 
            } 
        } 
        public IQueryable<WorkshopReport> Search(string Value) { 
            return _ctx.WorkshopReports.Where(i => i.ReportNumber.Contains(Value)); 
        } 
        public IQueryable<WorkshopReport> FindById(int Id) { 
            return _ctx.WorkshopReports.Where(i => i.Id == Id); 
        } 
    } 
}

Workshop控制器(Workshop Controller)

public ActionResult Edit(WorkshopReport value, FormCollection formvalue) { 
    try { 
        value.ShiftDate = _calenderRepository.ConvertPersianToEnglishFormat(formvalue["ShiftDate"].ToString()); 
        value.SubmitDateTime=DateTime.Now; 
        value.Type = internalType; 
        value.UserId= _userRepository.FindByEmail(User.Identity.Name).Id; 
        if (_workshopReportRepository.Edit(value)) { 
            _workshopReportRepository.Save(); 
            TempData["Success"] = "Updated"; 
        } 
    } catch (Exception) { 
        TempData["Error"] = "Try again..."; 
    } 
    return RedirectToAction("Index", "WorkshopReport", new { type = internalType }); 
}

错误原因分析

这个错误我之前也遇到过,核心是Entity Framework的上下文跟踪机制冲突:

  • 控制器中接收的value是模型绑定生成的全新WorkshopReport实例
  • 如果之前上下文已经加载过同一个Id的WorkshopReport实体(比如打开编辑页面时查询过该记录),EF会持续跟踪这个实体
  • 当你调用_ctx.Entry(updated).State = EntityState.Modified时,EF发现上下文已存在相同主键的跟踪实例,就会抛出主键冲突错误

解决方案

我推荐两种可靠的修复方式,你可以根据场景选择:

方案1:更新上下文已跟踪的实体(推荐)

修改仓储层的Edit方法,先从上下文获取已存在的实体,再将新实例的属性值复制过去,EF会自动跟踪属性变化:

public bool Edit(WorkshopReport updated) {
    try {
        // 从上下文获取已跟踪的实体
        var existingReport = _ctx.WorkshopReports.Find(updated.Id);
        if (existingReport == null) {
            return false; // 实体不存在
        }
        // 将新实例的属性值复制到已跟踪的实体上
        _ctx.Entry(existingReport).CurrentValues.SetValues(updated);
        // 不需要手动设置State,EF会自动标记为Modified
        return true;
    } catch (Exception ex) {
        // 记录日志
        return false;
    }
}

这种方式完全利用EF的跟踪机制,避免冲突,同时还能保留实体的原始状态(未修改的属性不会被更新)。

方案2:先分离已跟踪的实体再更新

如果一定要使用传入的新实例,可以先检查上下文是否已跟踪该实体,若存在则先分离,再标记为Modified:

public bool Edit(WorkshopReport updated) {
    try {
        var existingEntry = _ctx.Entry(updated);
        if (existingEntry.State == EntityState.Detached) {
            var trackedEntity = _ctx.WorkshopReports.Local.FirstOrDefault(e => e.Id == updated.Id);
            if (trackedEntity != null) {
                _ctx.Entry(trackedEntity).State = EntityState.Detached; // 分离已跟踪的实体
            }
            _ctx.Entry(updated).State = EntityState.Modified;
        }
        return true;
    } catch (Exception ex) {
        // 记录日志
        return false;
    }
}

注意:分离实体可能会影响其他依赖该实体的操作,仅在必要时使用。

额外优化建议

如果查询实体仅用于显示、不需要跟踪,可以在查询时加上AsNoTracking(),比如修改FindById方法:

public IQueryable<WorkshopReport> FindById(int Id) {
    return _ctx.WorkshopReports.Where(i => i.Id == Id).AsNoTracking();
}

这样上下文就不会跟踪该实体,后续更新新实例时就不会出现冲突。

内容的提问来源于stack exchange,提问作者Mehrdad Ghaffari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:25:48