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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 13:30:53