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

启用AutoInclude后EF Core删除实体出现跟踪冲突的解决方法

问题描述

实体类与EF配置

我有GdsReceptionBlackProduct、Employee和Customer三个实体类,代码定义如下:

public partial class GdsReceptionBlackProduct
{
    public int Id { get; set; }
    public string? CreatedBy { get; set; }
    public virtual Employee? CreatedByNavigation { get; set; }
}

public void Configure(EntityTypeBuilder<GdsReceptionBlackProduct> entity)
{
    entity.HasOne(d => d.CreatedByNavigation).WithMany(p => p.CreatedBy)
    .HasPrincipalKey(p => p.IdEmployee)
    .HasForeignKey(d => d.CreatedBy)
    .HasConstraintName("GDSReceptionBlackProduct_Employee_CreatedBy");
    entity.Navigation(e => e.CreatedByNavigation).AutoInclude();
}  

public partial class Employee
{
    public string IdEmployee { get; set; } = null!;
    public int? FkCountry { get; set; }
    public virtual Country Country { get; set; }
    public virtual ICollection<GdsReceptionBlackProduct> CreatedBy { get; } = new List<GdsReceptionBlackProduct>();
}
public void Configure(EntityTypeBuilder<Employee> entity)
{
    entity.HasOne(d => d.Country)
        .WithMany(p => p.Employees)
        .HasForeignKey(d => d.FkCountry)
        .HasConstraintName("FK_Employee_Country");
    entity.Navigation(e => e.Country).AutoInclude();
}
public partial class Customer
{
    public int? Fkcountry { get; set; }
    public virtual Country? Country { get; set; }
    public virtual ICollection<GdsReceptionBlackProduct> GdsReceptionBlackProduct { get; } = new List<GdsReceptionBlackProduct>();
}
public void Configure(EntityTypeBuilder<Customer> entity)
{
    entity.Navigation(e => e.Country).AutoInclude();
}

服务注入与删除逻辑

我采用工作单元+仓储模式结合依赖注入,在FrmGDSReceptionBlackProduct窗体中注入服务的代码如下:

public FrmGDSReceptionBlackProduct(GdsReceptionBlackProductService gdsReceptionBlackProductService,
                                       CustomerService customerService)
{
    InitializeComponent();
    this.gdsReceptionBlackProductService = gdsReceptionBlackProductService;
    this.customerService = customerService;
}

执行删除GdsReceptionBlackProduct实体的代码:

await gdsReceptionBlackProductService.RemoveGdsReceptionBlackProductAsync(Convert.ToInt32(txtID.Text));

// 泛型仓储中的RemoveAsync实现
public virtual async Task<bool> RemoveAsync(object id)
{
    T? exist = await dbSet.FindAsync(id);
    if (exist == null) throw new ArgumentNullException($"{nameof(RemoveAsync)} entity must not be null");
    dbSet.Remove(exist);
    return true;
}

错误信息

执行删除时出现如下错误:

id could not be removed: The instance of entity type 'Country' cannot be tracked because another instance with the key value '{IdCountry: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

注释掉entity.Navigation(e => e.CreatedByNavigation).AutoInclude();后删除可成功,但我需要保留AutoInclude来在GridView中显示关联属性,且所有CRUD操作都在同一窗体中,该如何解决?

更新信息

泛型仓储构造函数代码:

public class GenericRepository<T> : IGenericRepository<T>, IDisposable where T : class
{
    protected SIMContext _context;
    internal DbSet<T> dbSet;
    public GenericRepository(SIMContext context)
    {
        _context = context;
        _context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        dbSet = context.Set<T>();
    }
}
解决方案

原因分析

问题核心是实体跟踪冲突:

  1. 泛型仓储全局设置了QueryTrackingBehavior.NoTracking,但FindAsync方法会强制跟踪找到的实体。
  2. AutoInclude自动加载CreatedByNavigation(Employee)及其关联的Country,该Country会被上下文跟踪。
  3. 窗体中注入的CustomerService可能已加载过同ID的Country实体(Customer也配置了AutoInclude Country),导致同一上下文存在两个相同Key的Country跟踪实例,删除时触发冲突。

解决方法

方法1:删除前解除关联实体的跟踪

修改RemoveAsync方法,手动停止关联实体的跟踪状态:

public virtual async Task<bool> RemoveAsync(object id)
{
    T? exist = await dbSet.FindAsync(id);
    if (exist == null) throw new ArgumentNullException($"{nameof(RemoveAsync)} entity must not be null");

    // 针对GdsReceptionBlackProduct类型处理关联实体
    if (exist is GdsReceptionBlackProduct blackProduct)
    {
        if (blackProduct.CreatedByNavigation != null)
        {
            // 停止跟踪Employee
            _context.Entry(blackProduct.CreatedByNavigation).State = EntityState.Detached;
            // 停止跟踪关联的Country
            if (blackProduct.CreatedByNavigation.Country != null)
            {
                _context.Entry(blackProduct.CreatedByNavigation.Country).State = EntityState.Detached;
            }
        }
    }

    dbSet.Remove(exist);
    return true;
}

方法2:查询删除实体时禁用自动包含

删除操作不需要关联实体,查询时直接忽略AutoInclude:

public virtual async Task<bool> RemoveAsync(object id)
{
    // 忽略自动包含,仅加载主实体
    T? exist = await dbSet.IgnoreAutoIncludes()
                          .FirstOrDefaultAsync(e => EF.Property<object>(e, "Id") == id);
    if (exist == null) throw new ArgumentNullException($"{nameof(RemoveAsync)} entity must not be null");

    // 附加实体并标记为删除
    dbSet.Attach(exist);
    dbSet.Remove(exist);
    return true;
}

方法3:调整上下文生命周期

如果当前使用的是单例上下文,改为作用域上下文(每个操作实例化一个上下文),不同操作的上下文相互隔离,避免跨操作的跟踪冲突。

方法4:拆分查询逻辑

为GridView显示和删除操作分别提供不同的查询方法:

// 用于GridView显示,保留自动包含
public async Task<IEnumerable<T>> GetAllWithIncludesAsync()
{
    return await dbSet.AsTracking().ToListAsync();
}

// 用于删除/更新,禁用自动包含
public async Task<T?> GetByIdWithoutIncludesAsync(object id)
{
    return await dbSet.IgnoreAutoIncludes().FindAsync(id);
}

删除时调用GetByIdWithoutIncludesAsync获取实体再执行删除。


内容的提问来源于stack exchange,提问作者M.Bouabdallah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 22:32:08