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

EF Core拦截器未调用基类方法仍触发实体跟踪冲突错误排查

问题

在EF Core 7中实现了EnumEntitySaveInterceptor拦截器,意图移除ChangeTracker中具有相同Id的重复IEnumEntity类型条目。但调用SaveChanges时,执行代码var trackedEntities = context.ChangeTracker.Entries<IEnumEntity>()时,抛出错误:

The instance of entity type cannot be tracked because another instance of this type with the same key is already being tracked

尚未调用base.SavingChangesAsync(eventData, result, cancellationToken),却出现该错误。

拦截器代码

public class EnumEntitySaveInterceptor : SaveChangesInterceptor
{   
   public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData,
                                                                         InterceptionResult<int> result,
                                                                         CancellationToken cancellationToken = default)
   {
       var context = eventData.Context;

       // Get a list of all added and modified entities grouped by their type and Id
       var trackedEntities = context.ChangeTracker.Entries<IEnumEntity>()
           .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)
           .GroupBy(e => new { Type = e.Entity.GetType(), Id = GetEntityId(e.Entity) });

       // Loop through each group of entities with the same Type and Id
       foreach (var group in trackedEntities)
       {
           // If there are multiple entities with the same Type and Id,
           // detach all but the first one
           var entitiesToDetach = group.Skip(1).ToList();
           foreach (var entry in entitiesToDetach)
           {
               entry.State = EntityState.Detached;
           }
       }

       return base.SavingChangesAsync(eventData, result, cancellationToken);
   }

   public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
   {
       .... same logic as SavingChangesAsync
   }


   private int GetEntityId(object entity)
   {
       var type = entity.GetType();
       var idProperty = type.GetProperty("Id");
       if (idProperty != null && idProperty.PropertyType == typeof(int))
       {
           return (int)idProperty.GetValue(entity);
       }
       throw new InvalidOperationException($"Entity type {type} does not have an Id property of type int.");
   }
}

异常堆栈跟踪

at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
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, Nullable`1 fallbackState)
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.ChangeTracking.Internal.NavigationFixer.NavigationCollectionChanged(InternalEntityEntry entry, INavigationBase navigationBase, IEnumerable`1 added, IEnumerable`1 removed)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationCollectionChanged(InternalEntityEntry entry, INavigationBase navigationBase, IEnumerable`1 added, IEnumerable`1 removed)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry entry, INavigationBase navigationBase)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.LocalDetectChanges(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.DetectChanges()
at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.TryDetectChanges()
at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.Entries[TEntity]()
at MyProject.EntityFramework.Inteceptors.EnumEntitySaveInterceptor.SavingChangesAsync(DbContextEventData eventData, InterceptionResult`1 result, CancellationToken cancellationToken) in C:\Github\........
at Microsoft.EntityFrameworkCore.Diagnostics.CoreLoggerExtensions.SaveChangesStartingAsync(IDiagnosticsLogger`1 diagnostics, DbContext context, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__64.MoveNext()

原因分析

错误发生在调用ChangeTracker.Entries<IEnumEntity>()时,而非后续的base.SavingChangesAsync调用。核心原因是:

  • EF Core的ChangeTracker.Entries<T>()方法会自动触发DetectChanges(),用于同步实体状态与上下文的追踪状态。
  • 在DetectChanges()执行过程中,EF会遍历实体的导航属性,尝试将关联实体纳入ChangeTracker的追踪范围。
  • 如果此时存在两个同类型且主键相同的IEnumEntity实体(可能通过导航属性引入),EF会立即抛出重复追踪错误,这一过程早于自定义拦截逻辑的执行。

解决方案

绕过ChangeTracker.Entries<T>()自动触发的DetectChanges(),直接从EF Core内部的IStateManager获取实体条目,避免提前触发导航属性的自动追踪。

修改后的拦截器代码如下:

using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;

public class EnumEntitySaveInterceptor : SaveChangesInterceptor
{   
   public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData,
                                                                         InterceptionResult<int> result,
                                                                         CancellationToken cancellationToken = default)
   {
       var context = eventData.Context;
       var stateManager = context.GetService<IStateManager>();

       // 直接从StateManager获取实体,避免触发DetectChanges
       var trackedEntities = stateManager.GetEntries()
           .Where(e => e.Entity is IEnumEntity && (e.State == EntityState.Added || e.State == EntityState.Modified))
           .Select(e => new { Entry = e, Entity = (IEnumEntity)e.Entity })
           .GroupBy(x => new { Type = x.Entity.GetType(), Id = GetEntityId(x.Entity) });

       foreach (var group in trackedEntities)
       {
           var entitiesToDetach = group.Skip(1).ToList();
           foreach (var item in entitiesToDetach)
           {
               item.Entry.State = EntityState.Detached;
           }
       }

       return base.SavingChangesAsync(eventData, result, cancellationToken);
   }

   public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
   {
       var context = eventData.Context;
       var stateManager = context.GetService<IStateManager>();

       var trackedEntities = stateManager.GetEntries()
           .Where(e => e.Entity is IEnumEntity && (e.State == EntityState.Added || e.State == EntityState.Modified))
           .Select(e => new { Entry = e, Entity = (IEnumEntity)e.Entity })
           .GroupBy(x => new { Type = x.Entity.GetType(), Id = GetEntityId(x.Entity) });

       foreach (var group in trackedEntities)
       {
           var entitiesToDetach = group.Skip(1).ToList();
           foreach (var item in entitiesToDetach)
           {
               item.Entry.State = EntityState.Detached;
           }
       }

       return base.SavingChanges(eventData, result);
   }


   private int GetEntityId(object entity)
   {
       var type = entity.GetType();
       var idProperty = type.GetProperty("Id");
       if (idProperty != null && idProperty.PropertyType == typeof(int))
       {
           return (int)idProperty.GetValue(entity);
       }
       throw new InvalidOperationException($"Entity type {type} does not have an Id property of type int.");
   }
}

关键改动说明

  • 使用context.GetService<IStateManager>()获取EF Core内部状态管理器,直接访问已追踪的实体条目。
  • stateManager.GetEntries()不会自动触发DetectChanges(),因此可以在处理重复实体前避免导航属性的自动追踪,从而规避提前抛出的重复键错误。
  • 处理完重复实体后,再调用base.SavingChangesAsync,EF会正常执行后续的保存逻辑。

内容的提问来源于stack exchange,提问作者pantonis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 17:54:52