EF Core更新Absence实体触发System.ArgumentNullException问题排查
问题描述
更新Absence实体时抛出以下错误:
System.ArgumentNullException: Value cannot be null. (Parameter 'key') at System.Collections.Generic.Dictionary`2.FindValue(TKey key) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityReferenceMap.TryGet(Object entity, IEntityType entityType, InternalEntityEntry& entry, Boolean throwOnNonUniqueness) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.TryGetEntry(Object entity, IEntityType entityType, Boolean throwOnTypeMismatch) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.InitialFixup(InternalEntityEntry entry, Boolean fromQuery) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.FireStateChanged(EntityState oldState) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey) at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.SetEntityState(InternalEntityEntry entry, EntityState entityState) at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Update(TEntity entity)
Absence实体定义
public partial class Absence : IValidatableObject { public Absence() { AbsenceComments = new HashSet<AbsenceComment>(); } [Key] public int AbsenceId { get; set; } public int? AbsenceStatusId { get; set; } [DateTimeValidator] public DateTime CreateDate { get; set; } [DateTimeValidator] public DateTime LastModifiedDate { get; set; } public DateTime? AbsenceFrom { get; set; } public DateTime? AbsenceUntil { get; set; } public int? AbsenceTypeId { get; set; } public int? AbsenceDetailTypeId { get; set; } public int? CostCenterId { get; set; } public string Adlogin { get; set; } public int Personalnummer { get; set; } public virtual AbsenceDetailType? AbsenceDetailType { get; set; } public virtual AbsenceStatus? AbsenceStatus { get; set; } public virtual AbsenceType? AbsenceType { get; set; } public virtual CostCenter? CostCenter { get; set; } public virtual PresenceListRawData? PersonalnummerNavigation { get; set; } public virtual ICollection<AbsenceComment>? AbsenceComments { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { Absence absenceToCheck = (Absence)validationContext.ObjectInstance; if (absenceToCheck.AbsenceUntil > absenceToCheck.AbsenceFrom) { yield return new ValidationResult("结束日期必须晚于开始日期"); } } }
DbContext中的实体配置
modelBuilder.Entity<Absence>(entity => { entity.ToTable("Absence", "pre"); entity.HasKey(c => c.AbsenceId); entity.Property(e => e.AbsenceFrom).HasColumnType("datetime"); entity.Property(e => e.AbsenceUntil).HasColumnType("datetime"); entity.Property(e => e.Adlogin) .HasMaxLength(255) .HasColumnName("ADLogin"); entity.Property(e => e.CreateDate).HasColumnType("datetime"); entity.Property(e => e.LastModifiedDate).HasColumnType("datetime"); entity.HasOne(d => d.AbsenceDetailType) .WithMany(p => p.Absences) .HasForeignKey(d => d.AbsenceDetailTypeId) .HasConstraintName("FK__Absence__Absence__6CA64BFC"); entity.HasOne(d => d.AbsenceStatus) .WithMany(p => p.Absences) .HasForeignKey(d => d.AbsenceStatusId) .HasConstraintName("FK__Absence__Absence__66ED72A6"); entity.HasOne(d => d.AbsenceType) .WithMany(p => p.Absences) .HasForeignKey(d => d.AbsenceTypeId) .HasConstraintName("FK__Absence__Absence__67E196DF"); entity.HasOne(d => d.CostCenter) .WithMany(p => p.Absences) .HasForeignKey(d => d.CostCenterId) .HasConstraintName("FK__Absence__CostCen__68D5BB18"); entity.HasOne(d => d.PersonalnummerNavigation) .WithMany(p => p.Absences) .HasForeignKey(d => d.Personalnummer) .OnDelete(DeleteBehavior.ClientSetNull) .HasConstraintName("FK__Absence__Persona__6E8E946E"); });
更新实体的控制器方法
public IActionResult UpdateAbsences(int id, [FromBody] Absence absenceToUpdate) { if (this._context.Absences.Where(f => f.AbsenceId == id).Count() != 1) { return NotFound(); } if (TryValidateModel(absenceToUpdate)) { try { this._context.Absences.Update(absenceToUpdate); } catch (Exception e) { Console.WriteLine(e); } return Ok(true); } else { return BadRequest(ModelState); } }
请求的JSON数据
{ "absenceId": 4, "absenceStatusId": 1, "createDate": "2022-10-19T00:00:00", "lastModifiedDate": "2022-10-19T00:00:00", "absenceFrom": "2022-10-19T00:00:00", "absenceUntil": null, "absenceTypeId": 1, "absenceDetailTypeId": 1, "costCenterId": 1, "adlogin": "test\\hans", "personalnummer": 200, "absenceDetailType": { "absenceDetailTypeId": 1, "typeName": "Ferien 1 Tag", "comments": null, "isDeleted": false, "absenceTypeId": 1, "absenceType": { "absenceTypeId": 1, "typeName": "Ferien", "comments": null, "isDeleted": false, "absenceDetailTypes": [ null ], "absences": [ null ] }, "absences": [ null ] }, "absenceStatus": { "absenceStatusId": 1, "statusName": "Abgeschlossen", "comments": null, "isDeleted": false, "absences": [ null ] }, "absenceType": { "absenceTypeId": 1, "typeName": "Ferien", "comments": null, "isDeleted": false, "absenceDetailTypes": [ { "absenceDetailTypeId": 1, "typeName": "Ferien 1 Tag", "comments": null, "isDeleted": false, "absenceTypeId": 1, "absenceType": null, "absences": [ null ] } ], "absences": [ null ] }, "costCenter": { "costCenterId": 1, "costCenterName": "HR", "comments": "Test", "absences": [ null ] }, "personalnummerNavigation": null, "absenceComments": [], "absenceFromDate": "2022-10-19T22:00:00.000Z", "absenceUntilDate": null }
疑问:是否必须传递完整的外键实体,还是仅更新外键ID即可?
解决方案
错误原因分析
从调用栈和请求JSON来看,问题出在EF Core处理实体关联图时:
- 请求JSON中包含多层嵌套的关联实体(比如
absenceDetailType下嵌套absenceType,后者又包含absenceDetailTypes集合),且集合中存在null元素; - 调用
DbSet.Update()时,EF Core会自动遍历整个实体图,尝试附加所有关联实体到上下文,但遇到集合中的null对象时,会触发查找键值的操作,最终因key参数为null抛出异常。
关于外键更新的结论
不需要传递完整的外键实体,仅更新外键ID即可。EF Core只需要外键字段的值来维护关联关系,关联实体对象不是必填项,甚至应该避免传递,否则会增加不必要的实体追踪开销和出错概率。
具体修复方案
方案1:优化更新逻辑(推荐)
不要直接更新传入的Absence对象,先从数据库获取现有实体,再手动映射需要更新的属性:
public IActionResult UpdateAbsences(int id, [FromBody] Absence absenceToUpdate) { var existingAbsence = _context.Absences.Find(id); if (existingAbsence == null) { return NotFound(); } if (TryValidateModel(absenceToUpdate)) { try { // 仅映射需要更新的属性,跳过关联实体对象 existingAbsence.AbsenceStatusId = absenceToUpdate.AbsenceStatusId; existingAbsence.AbsenceTypeId = absenceToUpdate.AbsenceTypeId; existingAbsence.AbsenceDetailTypeId = absenceToUpdate.AbsenceDetailTypeId; existingAbsence.CostCenterId = absenceToUpdate.CostCenterId; existingAbsence.AbsenceFrom = absenceToUpdate.AbsenceFrom; existingAbsence.AbsenceUntil = absenceToUpdate.AbsenceUntil; existingAbsence.Adlogin = absenceToUpdate.Adlogin; existingAbsence.LastModifiedDate = DateTime.Now; // 建议更新为当前时间 _context.SaveChanges(); return Ok(true); } catch (Exception e) { Console.WriteLine(e); return StatusCode(500, "更新失败"); } } else { return BadRequest(ModelState); } }
方案2:清理请求JSON,避免传递多余关联实体
修改前端请求,只传递必要的属性和外键ID,移除所有关联实体对象:
{ "absenceId": 4, "absenceStatusId": 1, "createDate": "2022-10-19T00:00:00", "lastModifiedDate": "2022-10-19T00:00:00", "absenceFrom": "2022-10-19T00:00:00", "absenceUntil": null, "absenceTypeId": 1, "absenceDetailTypeId": 1, "costCenterId": 1, "adlogin": "test\\hans", "personalnummer": 200, "absenceComments": [] }
内容的提问来源于stack exchange,提问作者KTest
相关产品推荐
相关产品推荐

