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

EF Core调用DbSet.Add()时Change Tracker未自动递增ID问题

问题描述

尝试保存包含images字段(Image类型数组)的实体时遇到ID重复问题,具体情况如下:

Image实体定义

[Serializable]
public class Image
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string Url { get; set; }
    [Required]
    public bool IsMain { get; set; }
    public System.DateTime CreatedDate { get; set; }
    public System.DateTime UpdatedDate { get; set; }
}

自定义扩展方法AddIfNotExists

public static class DbSetExtensions
{
    public static EntityEntry<T> AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class
    {
        var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
        return exists ? null : dbSet.Add(entity);
    }
}

图片添加逻辑

private async Task AddImages(TVgnItmEst entity)
{
    if (entity.VgnItm.Images != null && entity.VgnItm.Images.Count > 0)
    {
        foreach (var image in entity.VgnItm.Images.ToList())
        {
            var resultingImage = images.AddIfNotExists(image, theImage => theImage.Url == image.Url);

            if (resultingImage != null)
            {
                VgnItmImage newVgnItmImage = new VgnItmImage { VgnItmId = entity.VgnItm.Id, ImageId = resultingImage.Entity.Id };

                vgnItmImages.AddIfNotExists(newVgnItmImage,
                    x => x.VgnItmId == entity.VgnItm.Id &&
                        x.ImageId == resultingImage.Entity.Id);
            }
        }
        await context.SaveChangesAsync();
    }
}

问题现象

传入的实体包含两个初始ID为0的新图片,调用AddIfNotExists两次后,Change Tracker给第二个图片的ID也设为1,导致保存时报错。已尝试配置Image实体ID为ValueGeneratedOnAdd,后改为UseIdentityAlwaysColumn并更新数据库,问题依旧。


问题原因与解决方案

核心原因

  1. AddIfNotExists仅校验数据库,忽略内存中已跟踪的实体:dbSet.Any(predicate)只会查询数据库中的数据,不会检查EF Change Tracker中已标记为Added但尚未保存到数据库的实体。当第一个图片被添加到DbSet后,数据库中还没有该记录,此时处理第二个图片时,若Url不同,方法会判定“不存在”并执行添加操作。
  2. EF临时ID生成冲突:对于初始ID为0的新实体,EF会分配临时ID用于内存跟踪。若未正确配置自增规则,或因重复添加触发异常跟踪逻辑,可能导致临时ID重复。

解决方案

1. 修改AddIfNotExists方法,同时校验本地跟踪实体

扩展方法需同时检查DbSet的Local集合(内存中已跟踪的实体)和数据库,避免重复添加未保存的实体:

public static class DbSetExtensions
{
    public static EntityEntry<T> AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class
    {
        bool exists;
        if (predicate != null)
        {
            var compiledPredicate = predicate.Compile();
            // 先检查本地已跟踪实体,再检查数据库
            exists = dbSet.Local.Any(compiledPredicate) || dbSet.Any(predicate);
        }
        else
        {
            exists = dbSet.Local.Any() || dbSet.Any();
        }
        return exists ? null : dbSet.Add(entity);
    }
}

2. 确保Image实体ID的自增配置正确

在实体类和DbContext中双重确认自增规则:

  • 给Image的Id属性添加自增特性:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
  • 在DbContext的OnModelCreating中补充配置:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Image>()
        .Property(i => i.Id)
        .ValueGeneratedOnAdd()
        .UseIdentityAlwaysColumn();
}

3. 验证传入的图片实体为独立实例

确认传入的两个图片是不同的对象实例,避免因引用相同导致EF跟踪逻辑异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 18:15:09