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

EF Core 6迁移导航属性报错:无法添加'ReadJob'导航属性

问题描述

从NHibernate迁移至基于.NET 6与SQL Server的EF Core 6,采用仓储模式逐个迁移仓储时,遇到导航属性相关错误:

System.InvalidOperationException: 无法向实体类型'ProductRead'添加属性或导航'ReadJob',因为该实体类型已存在同名属性或导航。

模型构建代码

modelBuilder.Entity<ProductRead>(entity =>
{
    entity.Property(e => e.Id).ValueGeneratedNever();
    entity.Property(e => e.ImageUrl).IsRequired(false);

    entity.Property(e => e.AbsDeviation)
        .HasColumnName("absDeviation")
        .HasComputedColumnSql("(abs([Quantity]-[ExpectedQuantity]))", false);

    entity.Property(e => e.ImageUrl).HasMaxLength(255);

    entity.Property(e => e.NegativeDeviation)
        .HasColumnName("negativeDeviation")
        .HasComputedColumnSql("(case when [ExpectedQuantity]>[Quantity] then [Quantity]-[ExpectedQuantity] else (0) end)", false);

    entity.Property(e => e.PositiveDeviation)
        .HasColumnName("positiveDeviation")
        .HasComputedColumnSql("(case when [Quantity]>[ExpectedQuantity] then [Quantity]-[ExpectedQuantity] else (0) end)", false);

    entity.Property(e => e.Sku).HasMaxLength(36);

    entity.Property(e => e.Version)
        .IsRowVersion()
        .IsConcurrencyToken();
    
    entity.HasOne(e => e.ReadJob)
        .WithMany(e => e.ProductReads)
        .HasForeignKey(e => e.ReadJobId)
        .HasConstraintName("FK_ProductReads_ReadJobId_ReadJob_Id")
        .IsRequired(false)
        .OnDelete(DeleteBehavior.Cascade);

    entity.HasIndex(e => e.Created, "IX_ProductReads_Created_Quantity_ReadJobId_Sku")
        .HasFillFactor(90);

    entity.HasIndex(e => e.ReadJob, "IX_ProductReads_ReadJobId_CreatedSkuQuantityExpectedQuantityImageUrlNoStockTransferVersion")
        .HasFillFactor(90);

    entity.HasIndex(e => e.ReadJob, "IX_ProductReads_ReadJobId_Incl_Sku")
        .HasFillFactor(80);

    entity.HasIndex(e => new { e.ReadJob, e.Sku }, "IX_ProductReads_ReadJobId_Sku_cover")
        .HasFillFactor(80);
});

modelBuilder.Entity<ReadJob>(entity =>
{
    entity.HasIndex(e => new { e.Assignment, e.ReadType, e.State }, "IX_ReadJobs_AssignmentId_ReadType_State")
        .HasFillFactor(80);

    entity.HasIndex(e => new { e.ReadType, e.Completed }, "IX_ReadJobs_ReadType_Completed_Created_UserId")
        .HasFillFactor(90);

    entity.HasIndex(e => e.State, "IX_ReadJobs_State_includes")
        .HasFillFactor(90);

    entity.HasIndex(e => new { e.UserId, e.ReadType, e.Completed, e.State }, "IX_ReadJobs_UserId_ReadType_Completed_State_includes")
        .HasFillFactor(90);

    entity.HasIndex(e => new { e.UserId, e.State }, "IX_ReadJobs_UserId_State_cover")
        .HasFillFactor(90);

    entity.HasIndex(e => e.Assignment, "NonClusteredIndex-20230503-081413")
        .HasFillFactor(80);

    entity.HasIndex(e => new { e.State, e.UserId }, "UX_ReadJobs_AssignmentId_IsCompleted_filtered")
        .IsUnique()
        .HasFilter("([State]='InProgress')")
        .HasFillFactor(85);

    entity.HasIndex(e => e.Assignment, "idx_ReadJob_Cover")
        .HasFillFactor(80);

    entity.HasIndex(e => new { e.ReadType, e.State }, "idx_readtype_state")
        .HasFillFactor(90);

    entity.Property(e => e.Id).ValueGeneratedNever();

    entity.Property(e => e.AssignmentId).IsRequired(false);

    entity.Ignore(e => e.ProductReads);


    entity.Property(e => e.ReadType)
        .HasMaxLength(20)
        .HasConversion(
            v => v.ToString(),
            v => (ReadType)Enum.Parse(typeof(ReadType), v));

    entity.Property(e => e.State)
        .HasMaxLength(255)
        .IsRequired(true)
        .HasConversion(
            v => v.ToString(),
            v => (ReadJobState)Enum.Parse(typeof(ReadJobState), v));

    entity.Property(e => e.Version)
        .IsRowVersion()
        .IsConcurrencyToken();

    entity.Ignore(e => e.Assignment);
    entity.HasOne(e => e.Assignment)
        .WithMany(e => e.ReadJobs)
        .HasForeignKey(e => e.AssignmentId)
        .HasConstraintName("FK_ReadJob_Assignment");
});

ProductRead实体类代码

public class ProductRead : EntityBase<Guid>
{
     public ProductRead()
     {
         this.ProductEntries = new List<ProductEntry>();
     }

     public virtual Guid? ReadJobId { get; set; }
     public virtual ReadJob ReadJob { get; set; }

     public virtual DateTime Created { get; set; }

     public virtual string Sku { get; set; }

     public virtual int? Quantity { get; set; }

     public virtual int ExpectedQuantity { get; set; }

     public virtual bool NoStockTransfer { get; set; }

     public virtual int? Deviation
     {
         get
         {
             if (this.Quantity != null)
             {
                 return this.Quantity.Value - this.ExpectedQuantity;
             }

             return null;
         }
     }        

     public virtual IList<ProductEntry> ProductEntries { get; protected set; }

     public virtual string ImageUrl { get; set; }
     public virtual int? AbsDeviation { get; }
     public virtual int? PositiveDeviation { get; }
     public virtual int? NegativeDeviation { get; }
}

ReadJob实体类代码

public class ReadJob : EntityBase<Guid> 
{
    public ReadJob()
    {
        this.ProductReads = new List<ProductRead>();
    }

    public virtual DateTime Created { get; set; }
    public virtual DateTime? Completed { get; set; }
    public virtual DateTime? Started { get; set; }
    public virtual int UserId { get; set; }
    public virtual ReadJobState State { get; set; }
    public virtual Guid? AssignmentId { get; set; }
    public virtual Assignment Assignment { get; set; }
    public virtual IList<ProductRead> ProductReads { get; set; }
    public virtual ReadType ReadType { get; set; }
}
问题原因
  1. ReadJob配置中的矛盾Ignore语句:先调用entity.Ignore(e => e.ProductReads);标记集合导航被忽略,但后续在ProductRead的配置中又通过HasOne(e => e.ReadJob).WithMany(e => e.ProductReads)关联该导航,EF Core处理双向关联时会尝试注册ProductReads,之前的Ignore操作导致元数据冲突,间接引发ReadJob导航的重复注册错误。同时ReadJob配置中先Ignore(e => e.Assignment)又立即配置HasOne(e => e.Assignment),进一步扰乱了EF Core的模型构建逻辑。
  2. 索引配置使用导航属性而非外键:在ProductRead的索引配置中,多次使用e.ReadJob(导航属性)创建索引,EF Core处理这类索引时会自动尝试注册或验证导航属性,重复触发的逻辑导致系统认为ReadJob导航被重复添加。
解决方案
  1. 移除ReadJob配置中多余的Ignore语句
    删除以下两行代码:

    entity.Ignore(e => e.ProductReads);
    entity.Ignore(e => e.Assignment);
    

    这两个导航属性需要用于建立关联,Ignore会和后续的关联配置产生冲突。

  2. 修改ProductRead的索引配置,使用外键属性
    将所有使用e.ReadJob的索引配置替换为使用外键e.ReadJobId:

    // 替换后
    entity.HasIndex(e => e.ReadJobId, "IX_ProductReads_ReadJobId_CreatedSkuQuantityExpectedQuantityImageUrlNoStockTransferVersion")
        .HasFillFactor(90);
    
    entity.HasIndex(e => e.ReadJobId, "IX_ProductReads_ReadJobId_Incl_Sku")
        .HasFillFactor(80);
    
    entity.HasIndex(e => new { e.ReadJobId, e.Sku }, "IX_ProductReads_ReadJobId_Sku_cover")
        .HasFillFactor(80);
    
  3. 验证模型一致性
    确保实体类中的导航属性与Fluent API配置完全匹配:ProductRead的ReadJob导航和ReadJob的ProductReads导航是双向关联,HasOne.WithMany的配置要准确对应实体类中的属性。

内容的提问来源于stack exchange,提问作者KnApP993

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 09:35:08