EF Core中非主键列实现自动递增的正确方法?
场景:现有表主键为GUID,需新增一个唯一且自动递增的整数字段MyIntegerField,以下针对两种方案逐一分析并解答疑问:
方案A分析
modelBuilder.Entity<MyTable>() .Property(x => x.MyIntegerField) .ValueGeneratedOnAdd() .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); modelBuilder.Entity<MyTable>() .HasIndex(x => x.MyIntegerField) .IsUnique();
疑问解答
数值何时递增?
当实体被添加到DbContext时,若MyIntegerField未手动赋值,EF Core会自动生成一个临时值(通常为负数),这个值会被直接写入数据库。保存完成后,由于设置了SetAfterSaveBehavior(PropertySaveBehavior.Ignore),EF不会将数据库中实际存储的值回写到实体对象。数值生成是在应用层还是数据库层?
属于应用层(客户端)生成。EF Core会在内存中生成临时值,而非依赖数据库的自动生成逻辑。这种方式在并发请求场景下,极大概率会出现多个请求生成相同值的情况,触发唯一索引约束异常,完全不符合高并发场景的需求。
方案B分析
modelBuilder.Entity<MyTable>() .Property(x => x.MyIntegerField) .ValueGeneratedOnAdd() .UseIdentityColumn(); modelBuilder.Entity<MyTable>() .HasIndex(x => x.MyIntegerField) .IsUnique();
疑问解答
该字段是否由数据库层管理?
是的。UseIdentityColumn()会让EF Core在数据库中为MyIntegerField创建标识列(Identity Column),值的生成完全由数据库负责,和该字段是否为主键无关——文档中提到适用于主键只是常见使用场景,主流数据库(SQL Server、MySQL、PostgreSQL等)均支持非主键的标识列配置,迁移后字段不会被设为主键是正常行为。使用此方法可能会遇到哪些问题?
- 标识列由数据库单表自增,大流量插入场景下会有轻微的性能损耗,但相较于方案A的并发冲突问题,影响可以忽略;
- 调用
SaveChanges()之前,实体的MyIntegerField值是EF生成的临时值,只有保存完成后才能获取到数据库生成的真实自增值; - 若需要手动插入自定义值到该字段,需先临时关闭数据库的标识列自动生成逻辑(如SQL Server中执行
SET IDENTITY_INSERT MyTable ON),否则会触发数据库报错; - 少数旧版数据库可能不支持非主键标识列,但主流数据库均无此限制。
结论
方案B是满足需求的正确实现方式。方案A依赖客户端生成值,无法应对并发场景下的唯一约束冲突;方案B利用数据库标识列的特性,由数据库保证值的自动递增和唯一性,是更可靠、符合生产环境要求的方案。
内容的提问来源于stack exchange,提问作者theuserthatpretendstobesmart

