EF6添加带Identity列的新条目时抛出乐观并发异常
问题原因说明
这个异常和实际的并发操作无关,是EF6的实体回查逻辑结合SQL Server DateTime精度特性共同导致的,具体原因如下:
- 你的
TestIdentity表使用ID + DTStamp作为复合主键,其中ID是Identity自增列,值由数据库生成。EF6插入该类型实体后,会自动执行回查逻辑:先执行INSERT语句插入数据,再用所有主键字段作为查询条件查询刚插入的行,把数据库生成的ID值回写到实体对象中,同时验证插入是否成功。 - 回查的条件会被EF拼接为:
WHERE ID = SCOPE_IDENTITY() AND DTStamp = @你传入的DTStamp参数 - C#的
DateTime.Now精度是100纳秒,而SQL Server的DATETIME类型精度只有3.33毫秒,你传入的DTStamp值存入数据库时会被自动截断精度。回查时用未截断的原始DateTime值去匹配数据库里截断后的值,自然匹配不到任何行,EF就会判定插入操作影响了0行,抛出你看到的乐观并发异常。 - 你直接执行SQL语句可以插入成功,是因为不需要触发EF的回查和验证逻辑,只要SQL本身符合语法约束就能执行成功。
解决方案
- 最优方案:调整表结构,仅用
ID作为唯一主键。Identity列本身已经可以唯一标识行,不需要将DTStamp加入主键,单独为DTStamp建立非聚集索引即可满足查询需求,从根源上避免该问题。 - 保留复合主键方案:将
DTStamp字段的类型改为DATETIME2(7),该类型的精度和C#的DateTime完全匹配,不会出现精度截断问题,回查时可以正常匹配到行。也可以手动配置实体的FluentAPI,明确主键各字段的生成规则:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<TestIdentity>() .HasKey(t => new { t.ID, t.DTStamp }); modelBuilder.Entity<TestIdentity>() .Property(t => t.ID) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); }
- 临时兼容方案:如果不需要EF回写自增ID到实体,可以将
ID属性的DatabaseGeneratedOption改为None,插入时手动给ID赋值,但会失去Identity列的自增特性。
内容的提问来源于stack exchange,提问作者SimonJ9
相关产品推荐
相关产品推荐

