代码之家  ›  专栏  ›  技术社区  ›  USMC6072

正在跟踪查找表的重复键值

  •  0
  • USMC6072  · 技术社区  · 2 年前

    我在Blazor Server应用程序中使用实体框架核心7和SQL Server。我知道这个错误有数千个线程,但我已经尽了一切努力来解决这个问题:

    无法跟踪实体类型“ChangeStatusType”的实例,因为已在跟踪另一个具有{'ChangeStatusTypeId‘}相同键值的实例。附着现有实体时,请确保仅附着一个具有给定键值的实体实例。

    我的实体关系是:

    • ChangeRequest 有很多 Changes
    • 每个 Change 可以有很多 ChangeStatus
    • 每个 更改状态 可以有一个 ChangeStatusType .

    如果我创建一个具有一个更改状态和一种更改状态类型的新更改,一切都会正常工作。

    如果我创建了多个更改,每个更改都有一个更改状态和一种更改状态类型,那么我就会遇到错误。

    这是我的相关部分 CREATE 方法,我正准备在其中保存ChangeStatus。在内部 FOREACH 它在第一次更改中运行得很好,然后在第二次尝试设置时抛出错误 changeStatus EntityState.Added 在这种情况下,每个 更改状态 只有一个 更改状态类型 但由于这是一个列表,我仍然需要循环浏览它。

    foreach (var change in addedChanges)
    {
        context.Entry(change).State = EntityState.Added;
    
        foreach (var changeStatus in change.ChangeStatuses)
        {
            context.ChangeTracker.TrackGraph(changeStatus, x => { x.Entry.State = EntityState.Added; });
            context.Attach(changeStatus.StatusTypes);
        }
    }
    
    context.AddRange(addedChanges);
    
    var result = await context.SaveChangesAsync().ConfigureAwait(false);
    

    我还尝试过使用 ChangeTrack.TrackGraph 设置的状态 更改状态 。以下是更改、状态和状态类型实体。这个 ChangeStatus.StatusTypeId 字段具有正确的值,并且 ChangeStatus.StatusType 实体也被填充。我仔细检查了一下 TypeId 存在于 更改状态类型 桌子

    public class ChangeStatus
    {
        #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
        /// <summary>
        /// Constructor used by EF.
        /// </summary>
        public ChangeStatus() { }
        #pragma warning restore CS8618 
    
        public int? ChangeStatusId { get; set; }
    
        [ForeignKey(nameof(ChangeDetail.ChangeDetailId))]
        public int? ChangeDetailId { get; set; }
    
        [Required]
        [Column(TypeName = "nvarchar(150)")] 
        public string SubmitUser { get; set; } = string.Empty;
    
        [Required]
        public DateTime StatusDate { get; set; }
    
        [ForeignKey(nameof(ChangeStatusType.ChangeStatusTypeId))]
        public int ChangeStatusTypeId { get; set; }
    
        public virtual ChangeStatusType StatusTypes { get; set; }
        public virtual List<ChangeDetail> Changes { get; set; }
    }
    
    public class ChangeStatusType
    {
        #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
        /// <summary>
        /// Constructor used by EF.
        /// </summary>
        public ChangeStatusType() { }
        #pragma warning restore CS8618 
    
        public int ChangeStatusTypeId { get; set; }
    
        [Required]
        [StringLength(50)]
        public string StatusTypeName { get; set; } = string.Empty;
    }
    

    更新:根据请求,这是包含Steve更改的代码块

    foreach (var change in addedChanges)
    {
        context.ChangeDetail.Add(change);
     // Change.ChangeStatuses will already be treated as "Added" but we don't want references to be treated as Add.
        foreach (var changeStatus in change.ChangeStatuses)
        {                                   //context.ChangeTracker.TrackGraph(changeStatus, x => { x.Entry.State = EntityState.Added; });
         var existingStatusType = context.ChangeStatusType.Local
         .FirstOrDefault(x => x.ChangeStatusTypeId == changeStatus.StatusTypes.ChangeStatusTypeId);
         if (existingStatusType != null)
           changeStatus.StatusTypes = existingStatusType;
        else
           context.Attach(changeStatus.StatusTypes);
        }
    
      foreach (var entry in context.ChangeTracker.Entries())
      {
        var entityName = entry.Entity.GetType().Name;
        var state = entry.State.ToString();
        Debug.WriteLine(state + " " + entityName);
      }
    }
    

    我在末尾添加了ForEach,以检查实体的状态及其显示的StatusTypes为added。

    0 回复  |  直到 2 年前
        1
  •  1
  •   Steve Py    2 年前

    在处理分离的实体,尤其是实体图时,在处理对现有数据的任何引用时,都需要格外小心。在一个更改中,您有将被添加的ChangeStatus条目,但每个更改状态都引用一个ChangeStatusType,这是一组有限的现有值。保存单个状态时,您可能会附加其状态类型,或者最终尝试插入新的状态类型。当插入两个状态时,只要状态具有不同的状态类型,它就可以工作,但否则会出现跟踪参考问题。

    foreach (var change in addedChanges)
    {
        // Change.ChangeStatuses will already be treated as "Added" but we don't want references to be treated as Add.
        foreach (var changeStatus in change.ChangeStatuses)
        {
            //context.ChangeTracker.TrackGraph(changeStatus, x => { x.Entry.State = EntityState.Added; });
            var existingStatusType = context.StatusType.Local
                .FirstOrDefault(x => x.StatusTypeId == changeStatus.StatusTypes.StatusTypeId);
            if (existingStatusType != null)
                changeStatus.StatusTypes = existingStatusType
            else
                context.Attach(changeStatus.StatusTypes);
        }
        // Edit: yes, the reference updates need to be done before adding the parent..
        context.Changes.Add(change);
    }
    

    本质上,这里的区别在于,我们不想将整个图视为已添加。添加更改时,ChangeStatues将自动被视为已添加,这是我们想要的,但我们不想要状态。StatusTypes被视为已添加。相反,我们根据 .Local 跟踪缓存,如果找到,则将引用替换为跟踪状态。如果它没有被跟踪,那么我们通过附加来跟踪它。当我们进入下一个更改状态时,当检查状态类型是否匹配时,它将解析跟踪的引用。

    推荐文章