在EF Core中更新实体时为何出现跟踪错误?
问题分析与解决方案
核心原因
你遇到的错误本质是:EF Core的ChangeTracker中已经存在一个拥有相同PatientCode(主键)的Patient实体实例,此时你又尝试让上下文跟踪另一个同主键的实例,导致跟踪冲突。
至于注释掉_applicationContext.Set<T>().Update(entity);后反而能正常保存,原因是:
当你调用SaveChangesAsync()时,EF Core只会持久化已被上下文跟踪的实体的变更。这说明当前请求的上下文里已经存在一个被跟踪的、与传入实体主键一致的Patient实例,并且这个实例的属性已经和你传入的实体同步了——大概率是你在调用UpdateAsync之前的代码中(比如权限校验、前置查询等)已经加载过该Patient实体,只是没在贴出的代码里体现。
而你调用Update(entity)时,EF Core会尝试把这个从请求体反序列化来的未被跟踪的实体附加到上下文并标记为Modified,但此时上下文里已有同主键的被跟踪实例,于是触发了冲突错误。
可行解决方案
方案1:先查询已有实体,再更新属性(推荐)
这是最稳妥的方式,彻底避免跟踪冲突:
public async Task<bool> UpdateAsync(T entity) { try { // 获取实体主键属性 var primaryKey = _applicationContext.Model.FindEntityType(typeof(T)) .FindPrimaryKey().Properties.First(); var keyValue = primaryKey.PropertyInfo.GetValue(entity); // 查询上下文已跟踪的实体,没有则从数据库加载 var existingEntity = await _applicationContext.Set<T>().FindAsync(keyValue); if (existingEntity == null) return false; // 将传入实体的属性值复制到被跟踪的实体上 _applicationContext.Entry(existingEntity).CurrentValues.SetValues(entity); await _applicationContext.SaveChangesAsync(); return true; } catch (Exception) { return false; } }
方案2:检查跟踪状态再处理
在执行Update前,先判断上下文是否已跟踪同主键实体,再决定操作:
public async Task<bool> UpdateAsync(T entity) { try { var primaryKey = _applicationContext.Model.FindEntityType(typeof(T)) .FindPrimaryKey().Properties.First(); var keyValue = primaryKey.PropertyInfo.GetValue(entity); // 检查ChangeTracker中是否已有同主键的跟踪实例 var trackedEntity = _applicationContext.ChangeTracker.Entries<T>() .FirstOrDefault(e => e.Property(primaryKey.Name).CurrentValue.Equals(keyValue)); if (trackedEntity != null) { // 已有跟踪实例,直接更新属性 trackedEntity.CurrentValues.SetValues(entity); } else { // 无跟踪实例,执行Update操作 _applicationContext.Set<T>().Update(entity); } await _applicationContext.SaveChangesAsync(); return true; } catch (Exception) { return false; } }
方案3:前置查询使用AsNoTracking
如果在调用UpdateAsync之前有查询该Patient的操作,给查询加上AsNoTracking,让上下文不跟踪该实例:
// 示例:仓储中的GetById方法增加AsNoTracking选项 public async Task<T> GetByIdAsync(string id, bool asNoTracking = false) { var query = _applicationContext.Set<T>().Where(e => e.PatientCode == id); if (asNoTracking) query = query.AsNoTracking(); return await query.FirstOrDefaultAsync(); }
内容的提问来源于stack exchange,提问作者Slepoyi
相关产品推荐
相关产品推荐

