You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 20:05:54