运行时动态数据模型乐观并发实现报错问题咨询
问题分析与解决方案
问题描述
数据库操作预期影响1行,但实际影响0行;提示实体加载后数据可能已被修改或删除。通过反射在运行时创建数据模型,CRUD操作正常,但实现乐观并发时触发该错误。为支持用户运行时修改/创建表,每个请求都会初始化DbContext以获取最新数据库架构版本。
核心原因
- 多DbContext上下文跟踪失效:每次请求新建DbContext时,新上下文无法继承前序上下文对实体并发令牌的跟踪状态。用新上下文更新实体时,EF无法验证加载时的原始并发令牌值,生成的SQL无法匹配数据库当前数据,触发并发冲突。
- 并发令牌配置不合理:将除ID外的所有属性设为并发令牌,意味着任何字段修改都会触发检查。但跨上下文更新时,这些字段的当前值可能已被其他请求修改,且新上下文无法获取加载时的原始值,直接导致冲突。
解决方案
1. 改用RowVersion作为并发令牌(推荐)
使用数据库自动维护的RowVersion(或Timestamp)字段作为并发令牌,实体更新时数据库会自动更新该字段值,无需手动维护,并发检查更高效。
修改动态实体创建逻辑,添加byte[]类型的RowVersion字段,并在OnModelCreating中配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); _onModelCreating(modelBuilder); var entitybuilder = modelBuilder.Entity(DynamicEntity); // 配置RowVersion为自动维护的并发令牌 var rowVersionProp = DynamicEntity.GetProperty("RowVersion"); if (rowVersionProp != null) { entitybuilder.Property(rowVersionProp.Name) .IsRowVersion(); } }
2. 同一DbContext内完成加载与更新
尽量在同一个DbContext实例中完成实体的加载和更新,EF会自动跟踪并发令牌的原始值:
using (var context = new DynamicDbContext(options, onModelCreating, cacheKey)) { var entity = context.Set(DynamicEntity).Find(id); // 修改实体属性 context.SaveChanges(); // 自动验证并发令牌 }
3. 跨上下文更新时手动维护并发令牌
必须跨上下文更新时,手动将实体附加到新上下文,并确保并发令牌的原始值被正确设置:
// 从旧上下文加载实体 var entity = oldContext.Set(DynamicEntity).Find(id); var originalRowVersion = entity.RowVersion; // 新建上下文处理更新 using (var newContext = new DynamicDbContext(options, onModelCreating, cacheKey)) { newContext.Attach(entity); // 修改实体属性 // 手动设置并发令牌的原始值 newContext.Entry(entity).Property("RowVersion").OriginalValue = originalRowVersion; newContext.Entry(entity).State = EntityState.Modified; try { newContext.SaveChanges(); } catch (DbUpdateConcurrencyException) { // 处理并发冲突,比如提示用户重新加载数据 } }
4. 确保模型缓存与数据库架构同步
用户修改表结构后,需清除与_cacheKey关联的模型缓存,确保新DbContext初始化时使用最新的模型定义,避免模型与数据库架构不匹配导致的并发检查失败。
内容的提问来源于stack exchange,提问作者Arul Kumar
相关产品推荐
相关产品推荐

