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

EF Core 7单向多对多对象插入更新的实体跟踪问题

EF Core 7添加Team对象时Position跟踪冲突问题

类结构

public class Team
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Player> Players { get; set; }
    public int SportId { get; set; }
    public Sport Sport { get; set; }
}

public class Player
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Position> Positions { get; set; }
    public int SportId { get; set; }
    public Sport Sport { get; set; }
}

public class Position
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Sport
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Player> Players { get; set; } = null!;
}

DbContext配置

public class AppDbContext : DbContext
{ 
    public DbSet<Team> Teams { get; set; }
    public DbSet<Player> Players { get; set; }
    public DbSet<Position> Positions { get; set; }
    public DbSet<Sport> Sports { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Data Source=localhost;Initial Catalog=Scrap;User Id=sa;Password=P@ssw0rd;TrustServerCertificate=true;");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Player>().HasOne(x => x.Sport).WithMany(x => x.Players).HasForeignKey(x => x.SportId).OnDelete(DeleteBehavior.NoAction);
        modelBuilder.Entity<Team>().HasMany(x => x.Players).WithMany();
        modelBuilder.Entity<Player>().HasMany(x => x.Positions).WithMany();
    }
}

添加方法

using (var context = await _contextFactory.CreateDbContextAsync())
{
    context.Teams.Add(team);
    await context.SaveChangesAsync();
}

待添加的JSON数据

{  
    "id": 0,  
    "name": "Fireball",  
    "players": [    
        {      
            "id": 8,      
            "name": "jim",      
            "positions": [        
                { "id": 1, "name": "Qb" },        
                { "id": 2, "name": "WR" },        
                { "id": 3, "name": "RB" }      
            ],      
            "sportId": 1,      
            "sport": {        
                "id": 1,        
                "name": "Football - american",        
                "players": [          
                    {            
                        "id": 9,            
                        "name": "joe",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    },          
                    {            
                        "id": 12,            
                        "name": "blake",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    },          
                    {            
                        "id": 13,            
                        "name": "erik",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    }        
                ]      
            }    
        },    
        {      
            "id": 9,      
            "name": "joe",      
            "positions": [        
                { "id": 1, "name": "Qb" },        
                { "id": 2, "name": "WR" },        
                { "id": 3, "name": "RB" }      
            ],      
            "sportId": 1,      
            "sport": {        
                "id": 1,        
                "name": "Football - american",        
                "players": [          
                    {            
                        "id": 8,            
                        "name": "jim",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    },          
                    null,          
                    {            
                        "id": 12,            
                        "name": "blake",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    },          
                    {            
                        "id": 13,            
                        "name": "erik",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    }        
                ]      
            }    
        },    
        {      
            "id": 12,      
            "name": "blake",      
            "positions": [        
                { "id": 1, "name": "Qb" },        
                { "id": 3, "name": "RB" }      
            ],      
            "sportId": 1,      
            "sport": {        
                "id": 1,        
                "name": "Football - american",        
                "players": [          
                    {            
                        "id": 8,            
                        "name": "jim",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    },          
                    {            
                        "id": 9,            
                        "name": "joe",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    },          
                    {            
                        "id": 13,            
                        "name": "erik",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    }        
                ]      
            }    
        },    
        {      
            "id": 13,      
            "name": "erik",      
            "positions": [        
                { "id": 1, "name": "Qb" },        
                { "id": 2, "name": "WR" },        
                { "id": 3, "name": "RB" }      
            ],      
            "sportId": 1,      
            "sport": {        
                "id": 1,        
                "name": "Football - american",        
                "players": [          
                    {            
                        "id": 8,            
                        "name": "jim",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    },          
                    {            
                        "id": 9,            
                        "name": "joe",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 2, "name": "WR" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    },          
                    {            
                        "id": 12,            
                        "name": "blake",            
                        "positions": [              
                            { "id": 1, "name": "Qb" },              
                            { "id": 3, "name": "RB" }            
                        ],            
                        "sportId": 1,            
                        "sport": null          
                    }        
                ]      
            }    
        }  
    ],  
    "sportId": 1,  
    "sport": {    
        "id": 1,    
        "name": "Football - american",    
        "players": [      
            {        
                "id": 8,        
                "name": "jim",        
                "positions": [          
                    { "id": 1, "name": "Qb" },          
                    { "id": 2, "name": "WR" },          
                    { "id": 3, "name": "RB" }        
                ],        
                "sportId": 1,        
                "sport": null      
            },      
            {        
                "id": 9,        
                "name": "joe",        
                "positions": [          
                    { "id": 1, "name": "Qb" },          
                    { "id": 2, "name": "WR" },          
                    { "id": 3, "name": "RB" }        
                ],        
                "sportId": 1,        
                "sport": null      
            },      
            {        
                "id": 12,        
                "name": "blake",        
                "positions": [          
                    { "id": 1, "name": "Qb" },          
                    { "id": 3, "name": "RB" }        
                ],        
                "sportId": 1,        
                "sport": null      
            },      
            {        
                "id": 13,        
                "name": "erik",        
                "positions": [          
                    { "id": 1, "name": "Qb" },          
                    { "id": 2, "name": "WR" },          
                    { "id": 3, "name": "RB" }        
                ],        
                "sportId": 1,        
                "sport": null      
            }    
        ]  
    }
}

报错信息

The instance of entity type 'Position' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked

问题分析与解决方案

问题根源

JSON反序列化后生成了多个相同Id的Position实例,EF Core上下文在跟踪这些实例时,发现同一主键对应的实体被多次创建,触发跟踪冲突。此外,JSON中携带了大量冗余的嵌套关联数据(如Player的Sport、Sport的Players),这些数据不仅无用,还会加重上下文跟踪负担。

解决方案

方案1:清理冗余数据,仅保留必要的主键关联

反序列化后先清理不需要的嵌套对象,只保留Position的Id、Player的Id及SportId,避免EF尝试跟踪多余的实体实例:

// 反序列化得到team对象后执行以下清理
foreach (var player in team.Players)
{
    // 只保留Position的Id,丢弃其他属性
    player.Positions = player.Positions.Select(p => new Position { Id = p.Id }).ToList();
    // 清除Player的嵌套Sport对象,只保留SportId
    player.Sport = null;
}
// 清除Team的嵌套Sport对象,只保留SportId
team.Sport = null;

using (var context = await _contextFactory.CreateDbContextAsync())
{
    // 提取所有唯一的Position Id
    var positionIds = team.Players.SelectMany(p => p.Positions).Select(p => p.Id).Distinct();
    // 附加所有涉及的Position,标记为未修改
    foreach (var id in positionIds)
    {
        context.Positions.Attach(new Position { Id = id }).State = EntityState.Unchanged;
    }
    // 附加所有涉及的Player,标记为未修改
    foreach (var player in team.Players)
    {
        context.Players.Attach(player).State = EntityState.Unchanged;
    }
    // 添加Team并保存
    context.Teams.Add(team);
    await context.SaveChangesAsync();
}

方案2:复用上下文跟踪的Position实例

从数据库获取已存在的Position,替换Player中的Position实例,确保每个Id只有一个被跟踪的对象:

using (var context = await _contextFactory.CreateDbContextAsync())
{
    // 提取所有需要的Position Id
    var positionIds = team.Players.SelectMany(p => p.Positions).Select(p => p.Id).Distinct();
    // 从数据库获取或附加这些Position
    var existingPositions = await context.Positions.Where(p => positionIds.Contains(p.Id)).ToListAsync();

    foreach (var player in team.Players)
    {
        // 用上下文跟踪的实例替换Player中的Position
        player.Positions = existingPositions.Where(p => player.Positions.Select(pp => pp.Id).Contains(p.Id)).ToList();
        // 附加Player并标记为未修改
        context.Players.Attach(player).State = EntityState.Unchanged;
    }
    // 附加Sport并标记为未修改
    context.Sports.Attach(new Sport { Id = team.SportId }).State = EntityState.Unchanged;
    // 清除Team的嵌套Sport对象
    team.Sport = null;

    // 添加Team并保存
    context.Teams.Add(team);
    await context.SaveChangesAsync();
}

方案3:禁用自动跟踪(不推荐,仅临时应急)

如果只需要创建Team与Player、Position的关联,不需要修改现有实体,可以临时禁用上下文的自动跟踪:

using (var context = await _contextFactory.CreateDbContextAsync())
{
    context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    
    // 清理冗余数据(同方案1)
    foreach (var player in team.Players)
    {
        player.Positions = player.Positions.Select(p => new Position { Id = p.Id }).ToList();
        player.Sport = null;
    }
    team.Sport = null;

    context.Teams.Add(team);
    await context.SaveChangesAsync();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 09:22:01