EF 6数据库优先模式下,如何正确添加含复合主键的实体?
EF6数据库优先模式下复合主键实体插入异常问题
错误信息
System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: 'Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.'
环境配置
- 采用EF6数据库优先(DB First)模式
- 表的复合主键由两列组成:
- RecordId:int类型自增列(identity)
- Created:datetime类型
- 两列均通过EDMX设置为实体键
示例代码
Dim ctx = New TestEntities Dim newTestRecord = New TestTable newTestRecord.Created = Now newTestRecord.DataPoint = "this is a test" ctx.TestTables.Add(newTestRecord) ctx.SaveChanges()
调用ctx.SaveChanges()时抛出上述异常。
已尝试的操作与观察
- 将RecordId设为单一主键,刷新EDMX后插入操作正常
- 原代码可与数据库交互:即使抛出异常,自增列RecordId仍会递增;若Created设为SQL datetime不合法值会报错
- 将Created设为单一主键,刷新EDMX后插入操作正常
核心疑问
- 添加含复合主键的实体是否需要特殊操作?
- 这种包含代码设置列与SQL Server自增列的复合主键方案是否可行?
补充背景:使用复合主键是因为需要对底层表进行分区,Created字段需作为聚集索引的一部分用于分区函数/方案。
问题原因与解决方案
1. 异常根源
EF6处理包含自增列的复合主键时,插入后会尝试用完整主键(RecordId+Created)匹配数据库行来验证插入结果。问题出在:
- 客户端设置的
Created = Now是本地高精度时间,而SQL Server的datetime类型精度仅为3.33ms,两者存储值存在精度差异 - 插入后EF用客户端缓存的Created值+数据库生成的RecordId查询匹配行,因精度差异查不到对应记录,就会抛出“影响0行”的并发异常
2. 复合主键方案的可行性
这种方案理论可行,但存在EF验证逻辑的坑,更推荐的替代方案是:
- 保留RecordId作为单一自增主键,承担唯一标识行的核心作用
- 单独创建包含Created字段的聚集索引用于分区,SQL Server允许聚集索引与主键分离,只需保证聚集索引键唯一(可添加唯一性约束)
主键的核心职责是唯一标识实体,分区需求无需绑定到主键上,分离设计更符合主键的本质作用。
3. 坚持使用复合主键的修复方法
若必须保留当前复合主键结构,可通过以下方式解决:
- 调整EDMX字段配置:将Created字段的
StoreGeneratedPattern设为None,明确告知EF该字段由客户端提供 - 统一时间精度:将客户端生成的Created值截断到SQL Server datetime的精度(3.33ms),或改用datetime2类型(更高精度,减少差异)
- 跳过EF行计数验证:重写上下文的
SaveChanges方法,对该实体跳过插入后的行匹配验证,或手动将实体状态设为Added后直接提交
内容的提问来源于stack exchange,提问作者Snake_Plissken
相关产品推荐
相关产品推荐

