Oracle含复合主键的多对多关联表更新问题及报错排查
问题解决方案
核心问题出在多对多关联表未设置复合主键,EF无法识别可写入的实体,因此标记为只读。同时原有代码的关系处理逻辑存在冗余和错误,以下是分步解决方法:
1. 修复数据库关联表的主键配置
找到你的多对多关联表(包含id_table1和id_table2的表),将这两个字段设置为复合主键。这是解决"The table/view does not have a primary key defined"错误的根本前提——EF必须通过主键跟踪实体的增删改操作。
2. 调整EF实体映射(二选一)
根据你的业务需求选择合适的映射方式:
方式A:隐式多对多关系(推荐,代码更简洁)
如果关联表仅包含两个外键字段,无需额外属性,可让EF自动管理关联表:
- 调整实体类的导航属性:
public class T_TPS_PARAMETERS { public decimal TPS_ID { get; set; } // 其他属性... public virtual ICollection<T_COMPANY> T_COMPANY { get; set; } = new HashSet<T_COMPANY>(); } public class T_COMPANY { public decimal ID_COMPANY { get; set; } // 其他属性... public virtual ICollection<T_TPS_PARAMETERS> T_TPS_PARAMETERS { get; set; } = new HashSet<T_TPS_PARAMETERS>(); }
- 在DbContext的
OnModelCreating中配置关系:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<T_TPS_PARAMETERS>() .HasMany(t => t.T_COMPANY) .WithMany(c => c.T_TPS_PARAMETERS) .Map(m => { m.ToTable("你的关联表名称"); // 替换为实际表名 m.MapLeftKey("id_table1"); // 对应T_TPS_PARAMETERS的外键 m.MapRightKey("id_table2"); // 对应T_COMPANY的外键 }); }
方式B:显式关联表实体(适合关联表有额外字段的场景)
如果关联表有其他业务字段,需创建显式实体并配置复合主键:
- 创建关联表实体:
public class T_TPS_COMPANY { [Key, Column(Order = 0)] public decimal TPS_ID { get; set; } // 对应id_table1 [Key, Column(Order = 1)] public decimal ID_COMPANY { get; set; } // 对应id_table2 // 额外业务字段... // 导航属性 public virtual T_TPS_PARAMETERS TPS_PARAMETER { get; set; } public virtual T_COMPANY COMPANY { get; set; } }
- 在DbContext中配置关系:
public DbSet<T_TPS_COMPANY> T_TPS_COMPANY { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<T_TPS_COMPANY>() .HasRequired(tc => tc.TPS_PARAMETER) .WithMany(t => t.T_TPS_COMPANY) .HasForeignKey(tc => tc.TPS_ID); modelBuilder.Entity<T_TPS_COMPANY>() .HasRequired(tc => tc.COMPANY) .WithMany(c => c.T_TPS_COMPANY) .HasForeignKey(tc => tc.ID_COMPANY); }
- 调整主实体的导航属性:
// T_TPS_PARAMETERS中 public virtual ICollection<T_TPS_COMPANY> T_TPS_COMPANY { get; set; } = new HashSet<T_TPS_COMPANY>(); // T_COMPANY中 public virtual ICollection<T_TPS_COMPANY> T_TPS_COMPANY { get; set; } = new HashSet<T_TPS_COMPANY>();
3. 修正更新逻辑代码
针对方式A(隐式多对多)的更新代码
直接操作导航属性,无需手动删除插入:
public void Update(T_TPS_PARAMETERS model) { using (var _context = new CostingAppDb()) { try { var existingTps = _context.T_TPS_PARAMETERS .Include(t => t.T_COMPANY) // 预加载关联数据 .FirstOrDefault(t => t.TPS_ID == model.TPS_ID); if (existingTps == null) throw new ArgumentException("目标TPS参数不存在"); // 清空旧关联 existingTps.T_COMPANY.Clear(); // 添加新关联(仅Attach已存在的公司,避免重复插入) foreach (var company in model.T_COMPANY) { var existingCompany = _context.T_COMPANY.Attach(new T_COMPANY { ID_COMPANY = company.ID_COMPANY }); existingTps.T_COMPANY.Add(existingCompany); } // 更新TPS参数自身属性 _context.Entry(existingTps).CurrentValues.SetValues(model); _context.SaveChanges(); } catch (Exception ex) { throw ex; } } }
针对方式B(显式关联表实体)的更新代码
直接操作关联表实体,删除旧关系后插入新关系:
public void Update(T_TPS_PARAMETERS model) { using (var _context = new CostingAppDb()) { try { var tpsId = model.TPS_ID; // 删除该TPS的所有旧关联 var oldRelationships = _context.T_TPS_COMPANY.Where(tc => tc.TPS_ID == tpsId); _context.T_TPS_COMPANY.RemoveRange(oldRelationships); // 添加新关联 foreach (var company in model.T_COMPANY) { _context.T_TPS_COMPANY.Add(new T_TPS_COMPANY { TPS_ID = tpsId, ID_COMPANY = company.ID_COMPANY }); } // 更新TPS参数自身属性 var existingTps = _context.T_TPS_PARAMETERS.FirstOrDefault(t => t.TPS_ID == tpsId); if (existingTps != null) { _context.Entry(existingTps).CurrentValues.SetValues(model); } _context.SaveChanges(); } catch (Exception ex) { throw ex; } } }
额外注意事项
- 用
using包裹DbContext,自动管理上下文生命周期,避免连接泄漏。 - 原有代码中
Add后又Attach的操作是错误的,会导致EF跟踪状态混乱,已存在的实体用Attach,新实体用Add。 - 显式配置外键属性可让EF更清晰地跟踪关系,减少异常发生概率。
内容的提问来源于stack exchange,提问作者Claudiu Dascalu
相关产品推荐
相关产品推荐

