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并更新数据库,问题依旧。
问题原因与解决方案
核心原因
AddIfNotExists仅校验数据库,忽略内存中已跟踪的实体:dbSet.Any(predicate)只会查询数据库中的数据,不会检查EF Change Tracker中已标记为Added但尚未保存到数据库的实体。当第一个图片被添加到DbSet后,数据库中还没有该记录,此时处理第二个图片时,若Url不同,方法会判定“不存在”并执行添加操作。- 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
相关产品推荐
相关产品推荐

