EF Core保存实体报错:IDENTITY_INSERT为OFF时无法插入标识列
背景
我有一个Category模型类,通过可空的ParentId属性关联父分类,使用EF Core 7的Fluent API实现模型与数据库的映射,未采用EF迁移,而是手动编写建表脚本。
手动建表脚本
CREATE TABLE [Categories] ( [Id] [bigint] IDENTITY(1,1) NOT NULL, [Created] [datetime2](0) NOT NULL, [ParentId] [bigint] NULL, [Name] [nvarchar](255) NOT NULL, CONSTRAINT [PK_Categories_Id] PRIMARY KEY CLUSTERED ([Id] ASC) ) ALTER TABLE [Categories] WITH CHECK ADD CONSTRAINT [FK_Cat_Cat] FOREIGN KEY([ParentId]) REFERENCES [Categories] ([Id])
Category模型类
public class Category { public long Id { get; } public string Name { get; set; }; public Category? Parent { get; set; } public long? ParentId { get; set; } public ICollection<Category>? Categories { get; set; } = new List<Category>(); }
EF Core映射配置类
public class CategoryConfiguration : IEntityTypeConfiguration<Category> { public void Configure(EntityTypeBuilder<Category> builder) { builder.ToTable("Categories"); builder.HasKey(it => it.Id); builder.Property(it => it.Id).ValueGeneratedNever(); builder.Property(it => it.Created).IsRequired(); builder.Property(it => it.Name).IsRequired(); builder.HasOne(it => it.Parent) .WithMany(c => c.Categories) .HasForeignKey(d => d.ParentId) .IsRequired(false); } } }
应用层代码
var parentId = 1; // 数据库中已存在的分类ID Category? parent = await _repository.GetById(parentId, cancellationToken); var category = new Category { Name = "test", Parent = parent // 注释掉这行就能成功保存 }; await _repository.Insert(category); try { await _repository.SaveAsync(); } catch (DbUpdateException ex) { // 报错:Cannot insert explicit value for identity column in table 'Categories' when IDENTITY_INSERT is set to OFF. }
问题现象
我已在映射中为Id属性设置ValueGeneratedNever(),将ID生成交由数据库负责,甚至手动执行SET IDENTITY_INSERT [Categories] ON,但仍收到报错:
Cannot insert explicit value for identity column in table 'Categories' when IDENTITY_INSERT is set to OFF.
已尝试的配置
我在映射类中尝试了以下几种Id属性配置方式,问题依然存在:
- builder.Property(it => it.Id).UseIdentityColumn(1,1); - builder.Property(it => it.Id).UseIdentityColumn(); - builder.Property(it => it.Id).ValueGeneratedNever(); - builder.Property(it => it.Id).ValueGeneratedAdd();
仓储的Insert方法是对DbContext.AddAsync的简单封装:
public async Task Insert(T entity) { await _dbContext.Set<T>().AddAsync(entity); }
核心原因
当你把已存在的Parent实体关联到新创建的Category时,EF Core的AddAsync方法会递归标记所有关联的未追踪实体为Added状态,导致EF尝试重新插入已存在的Parent实体,而Parent的Id是数据库自增列,因此触发报错。
修复步骤
修改仓储插入逻辑
不要直接使用AddAsync,改用直接设置实体状态为Added的方式,避免递归标记关联实体:public async Task Insert(T entity) { _dbContext.Set<T>().Entry(entity).State = EntityState.Added; // 复杂关联场景也可以用Attach后设置状态: // _dbContext.Set<T>().Attach(entity); // _dbContext.Entry(entity).State = EntityState.Added; }修正Id的映射配置
数据库中Id是自增列,应该用UseIdentityColumn()匹配数据库设置,而不是ValueGeneratedNever():builder.Property(it => it.Id).UseIdentityColumn();可选优化:直接设置ParentId
如果不需要完整的Parent实体,直接设置ParentId可以避免状态追踪问题:var category = new Category { Name = "test", ParentId = parentId // 直接赋值父ID,无需加载Parent实体 };
补充说明
AddAsync会将实体及其所有关联的未追踪实体标记为Added,这是关联父实体后触发错误的根本原因。UseIdentityColumn()会明确告诉EF Core,Id由数据库自增生成,插入时不会尝试手动设置该值,完全匹配建表脚本的逻辑。
内容的提问来源于stack exchange,提问作者user1765862

