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

Entity Framework 6保存复杂对象时重复实体跟踪错误求助

问题:SQLite测试时AddRange()抛出重复跟踪实体错误

背景

正在为C#/SQL Server应用编写单元测试,采用SQLite做非生产环境测试,已将生产样本数据导出为JSON用于填充测试库。

错误现象

解析JSON得到复杂对象的IEnumerable后,调用DbContext.AddRange()时抛出以下错误:

System.InvalidOperationException : The instance of entity type 'RequestType' cannot be tracked because another instance with the same key value for {'RequestTypeId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

问题根源是重复保存查找表数据:RequestType是与Request关联的单值查找表,Status是多值查找表,ChangeDetail是关联Request的唯一ID详情对象。曾尝试将RequestType和Statuses设为Null,未解决问题。

实体类及配置

Request类定义

public class Request
{
    public int RequestId { get; set; }
    /// <summary>changed from "navarchar(max)" to "text" for sqlite testing db </summary>
    [Required]
    [MinLength(50)]
    [Column(TypeName = "text")]
    public string Justification { get; set; } = string.Empty;
    [Required]
    public int SubmitUser { get; set; }
    [Required]
    public DateTime SubmitDate { get; set; }
    public int? ModifyUser { get; set; }
    public DateTime? ModifyDate { get; set; }
    public virtual IEnumerable<ChangeDetail> ChangeDetail { get; set; } = new List<ChangeDetail>();
    public virtual IEnumerable<Status> Statuses { get; set; } = new List<Status>();
    public virtual RequestType RequestType { get; set; } = new RequestType();
}

实体关系配置

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Request>()
            .HasOne(x => x.RequestType)
            .WithMany(x => x.Requests);

    modelBuilder.Entity<Request>()
            .HasMany(x => x.ChangeDetail)
            .WithOne(x => x.Request);

    modelBuilder.Entity<Request>()
            .HasMany(x => x.Statuses)
            .WithMany(x => x.Requests);

    modelBuilder.Entity<Status>()
            .HasOne(x => x.RequestStatusType)
            .WithMany(x => x.Statuses);
}

已尝试的无效方法

  • 使用LINQ对查找表实体去重
  • 设置实体状态为EntityState.Unmodified

解决方案建议

1. 预插入并复用查找表实体

在插入Request之前,先提取所有唯一的RequestType和Status实体插入上下文,确保每个键仅被跟踪一次,之后将Request中的引用替换为上下文已跟踪的实例:

// 提取唯一RequestType并插入
var uniqueRequestTypes = requests.Select(r => r.RequestType)
    .DistinctBy(rt => rt.RequestTypeId)
    .ToList();
context.RequestTypes.AddRange(uniqueRequestTypes);
context.SaveChanges();

// 提取唯一Status并插入
var uniqueStatuses = requests.SelectMany(r => r.Statuses)
    .DistinctBy(s => s.StatusId)
    .ToList();
context.Statuses.AddRange(uniqueStatuses);
context.SaveChanges();

// 替换Request中的引用
foreach (var request in requests)
{
    request.RequestType = uniqueRequestTypes.First(rt => rt.RequestTypeId == request.RequestType.RequestTypeId);
    request.Statuses = uniqueStatuses.Where(s => request.Statuses.Any(rs => rs.StatusId == s.StatusId)).ToList();
}

context.Requests.AddRange(requests);
context.SaveChanges();

2. 手动Attach查找表实体

遍历Request时,检查上下文是否已跟踪对应键的查找表实体,已存在则复用,不存在则Attach而非Add:

foreach (var request in requests)
{
    // 处理RequestType
    var existingRequestType = context.RequestTypes.Local
        .FirstOrDefault(rt => rt.RequestTypeId == request.RequestType.RequestTypeId);
    if (existingRequestType != null)
    {
        request.RequestType = existingRequestType;
    }
    else
    {
        context.Attach(request.RequestType);
    }

    // 处理Statuses
    var updatedStatuses = new List<Status>();
    foreach (var status in request.Statuses)
    {
        var existingStatus = context.Statuses.Local
            .FirstOrDefault(s => s.StatusId == status.StatusId);
        if (existingStatus != null)
        {
            updatedStatuses.Add(existingStatus);
        }
        else
        {
            context.Attach(status);
            updatedStatuses.Add(status);
        }
    }
    request.Statuses = updatedStatuses;
}

context.Requests.AddRange(requests);
context.SaveChanges();

3. 清除查找表实体跟踪状态

如果查找表在测试中为只读,插入后可清除上下文跟踪,避免后续重复跟踪:

// 插入查找表后清除跟踪
context.RequestTypes.AddRange(uniqueRequestTypes);
context.SaveChanges();
context.ChangeTracker.Clear();

context.Statuses.AddRange(uniqueStatuses);
context.SaveChanges();
context.ChangeTracker.Clear();

// 再插入Request
context.Requests.AddRange(requests);
context.SaveChanges();

4. 修复实体默认初始化问题

Request类中默认初始化RequestType = new RequestType()可能生成空键实例,建议改为可空类型避免多余实例:

public virtual RequestType? RequestType { get; set; }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 18:33:11