更新数据库条目时遇实体附加异常:同类型实体主键冲突
解决EF实体附加冲突:
Attaching an entity of type 'ProgramLanguage' failed... 这个错误是Entity Framework里非常典型的上下文跟踪冲突问题——当你通过GetProgramLanguageById从数据库获取实体时,这个coreProgramLanguage已经被当前的UnitOfWork上下文跟踪了。之后你又创建了一个拥有相同主键的newCore实体,尝试更新时EF发现上下文里已经存在一个同主键的被跟踪实体,于是抛出了这个异常。
咱们先拆解下你代码逻辑里的冗余和问题点:
- 你先获取了被上下文跟踪的
coreProgramLanguage,接着把它转成DM再转回实体得到detachedCore——这一步完全没必要,纯粹绕了弯路 - 然后将传入的业务模型转成
newCore,赋值原有RecordDate和TestId后尝试更新 - 此时
newCore的主键和已被跟踪的coreProgramLanguage完全一致,EF不允许同一个上下文里存在两个同主键的被跟踪实体
解决方案一:直接修改已跟踪实体(最推荐)
既然coreProgramLanguage已经被上下文跟踪,直接把业务模型的字段值映射到这个实体上就好,EF会自动记录所有变更,不需要创建新实体。这种方式既简洁又从根源避免了跟踪冲突:
public bool UpdateProgramLanguage(ProgramLanguageDm programLanguage) { using (var iUnitOfWork = new UnitOfWork<TourismEntities>()) { var coreProgramLanguage = iUnitOfWork.CommonHandler.GetProgramLanguageById(programLanguage.ProgramLanguageId); if (coreProgramLanguage == null) { return false; } // 直接把业务模型的字段映射到已跟踪的实体,保留原有RecordDate和TestId coreProgramLanguage.ProgramLanguageName = programLanguage.ProgramLanguageName; coreProgramLanguage.ProgramLanguageLevel = programLanguage.ProgramLanguageLevel; // 如果你的Repository.Update只是标记实体为Modified,其实EF已经自动跟踪了变更 // 如果UnitOfWork在Dispose前会自动执行SaveChanges,甚至可以省略Update调用 iUnitOfWork.CommonHandler.UpdateProgramLanguage(coreProgramLanguage); return true; } }
解决方案二:分离原有实体后附加新实体
如果你一定要用新创建的newCore实体,可以先把原来的coreProgramLanguage从上下文分离,再附加新实体并标记为修改状态:
public bool UpdateProgramLanguage(ProgramLanguageDm programLanguage) { using (var iUnitOfWork = new UnitOfWork<TourismEntities>()) { var coreProgramLanguage = iUnitOfWork.CommonHandler.GetProgramLanguageById(programLanguage.ProgramLanguageId); if (coreProgramLanguage == null) { return false; } // 把原有被跟踪的实体从上下文分离 iUnitOfWork.Context.Entry(coreProgramLanguage).State = EntityState.Detached; var newCore = GeneralMigrate.ProgramLanguageDmToEntity(programLanguage); // 保留原有记录日期和TestId newCore.RecordDate = coreProgramLanguage.RecordDate; newCore.TestId = coreProgramLanguage.TestId; // 附加新实体并标记为修改状态 iUnitOfWork.Context.Entry(newCore).State = EntityState.Modified; iUnitOfWork.SaveChanges(); // 假设你的UnitOfWork提供SaveChanges方法 return true; } }
解决方案三:获取不被跟踪的实体
在查询的时候使用AsNoTracking(),这样获取的实体不会被上下文跟踪,之后创建同主键的新实体就不会产生冲突:
首先修改你的GetProgramLanguageById方法(如果允许修改的话):
public ProgramLanguage GetProgramLanguageById(int id) { return _context.ProgramLanguages.AsNoTracking().FirstOrDefault(p => p.ProgramLanguageId == id); }
然后业务层代码可以保留类似原来的逻辑,但去掉多余的DM来回转换:
public bool UpdateProgramLanguage(ProgramLanguageDm programLanguage) { using (var iUnitOfWork = new UnitOfWork<TourismEntities>()) { var coreProgramLanguage = iUnitOfWork.CommonHandler.GetProgramLanguageById(programLanguage.ProgramLanguageId); if (coreProgramLanguage == null) { return false; } var newCore = GeneralMigrate.ProgramLanguageDmToEntity(programLanguage); newCore.RecordDate = coreProgramLanguage.RecordDate; newCore.TestId = coreProgramLanguage.TestId; return iUnitOfWork.CommonHandler.UpdateProgramLanguage(newCore); } }
额外优化建议
你的模型迁移代码里,ProgramLanguageEntityToDm和ProgramLanguageDmToEntity可以简化,另外注意到DataModel里的RecordDate是非可空的,而DbModel是可空的,迁移时的?? new DateTime()处理是合理的,避免了空值问题。
附:你提供的相关代码格式化
DbModel:
public partial class ProgramLanguage { public int ProgramLanguageId { get; set; } public int TestId { get; set; } public string ProgramLanguageName { get; set; } public string ProgramLanguageLevel { get; set; } public Nullable<System.DateTime> RecordDate { get; set; } public virtual Test Test { get; set; } }
DataModel:
[DataContract] [Serializable] public class ProgramLanguageDm { public ProgramLanguageDm() { Test = new Test(); } [DataMember] public int ProgramLanguageId { get; set; } [DataMember] public int TestId { get; set; } [DataMember] public string ProgramLanguageName { get; set; } [DataMember] public string ProgramLanguageLevel { get; set; } [DataMember] public DateTime RecordDate { get; set; } [DataMember] public Test Test { get; set; } }
数据库上下文交互代码:
public bool UpdateProgramLanguage(ProgramLanguage programLanguage) { _repository.Update(programLanguage); return true; }
业务层代码(原版本):
public bool UpdateProgramLanguage(ProgramLanguageDm programLanguage) { using (var iUnitOfWork = new UnitOfWork<TourismEntities>()) { var coreProgramLanguage = iUnitOfWork.CommonHandler.GetProgramLanguageById(programLanguage.ProgramLanguageId); if (coreProgramLanguage == null) { return false; } var detachedCore = GeneralMigrate.ProgramLanguageDmToEntity(GeneralMigrate.ProgramLanguageEntityToDm(coreProgramLanguage)); var newCore = GeneralMigrate.ProgramLanguageDmToEntity(programLanguage); newCore.RecordDate = detachedCore.RecordDate; newCore.TestId = detachedCore.TestId; return iUnitOfWork.CommonHandler.UpdateProgramLanguage(newCore); } }
模型迁移代码:
public static ProgramLanguageDm ProgramLanguageEntityToDm(ProgramLanguage entity) { return entity != null ? new ProgramLanguageDm { TestId = entity.TestId, ProgramLanguageId = entity.ProgramLanguageId, ProgramLanguageLevel = entity.ProgramLanguageLevel, ProgramLanguageName = entity.ProgramLanguageName, RecordDate = entity.RecordDate ?? new DateTime(), } : new ProgramLanguageDm(); } public static ProgramLanguage ProgramLanguageDmToEntity(ProgramLanguageDm dm) { return dm != null ? new ProgramLanguage { TestId = dm.TestId, ProgramLanguageId = dm.ProgramLanguageId, ProgramLanguageLevel = dm.ProgramLanguageLevel, ProgramLanguageName = dm.ProgramLanguageName } : new ProgramLanguage(); }
内容的提问来源于stack exchange,提问作者oguzhn
相关产品推荐
相关产品推荐

