【问题标题】:EF Core type configuration adds redundant FKsEF Core 类型配置添加冗余 FK
【发布时间】:2021-08-22 08:14:48
【问题描述】:

我正在尝试在 EFCore 中配置与实体之间的关系:SquarePosition。 Position 与名为squares 的正方形之间存在一对(位置)对多(正方形)关系,在名为enPassentTakablePawnOfWhiteSquareenPassentTakablePawnOfBlack 之间存在两个一对一关系。请注意,两个实体中的 FK 和 PK 都是影子属性:

public class PositionTypeConfiguration : IEntityTypeConfiguration<Position>
    {
        public void Configure(EntityTypeBuilder<Position> builder)
        {
            builder.ToTable("Positions");
            builder.Property<Guid>("BoardId");
            builder.Property<Guid?>("CurrentPositionId");
            builder.HasOne<Board>().WithMany("previosPositions").HasForeignKey("BoardId");
            builder.HasOne<Board>().WithOne("currentPosition").IsRequired(false).HasForeignKey<Position>("CurrentPositionId");

            builder.HasMany<Square>("squares").WithOne().HasForeignKey("PositionId");
            builder.Property<Guid?>("EnPassentTakableWhitePawnId");
            builder.Property<Guid?>("EnPassentTakableBlackPawnId");
            builder.HasOne<Square?>("enPassentTakablePawnOfWhite").WithOne().HasForeignKey<Position>("EnPassentTakableWhitePawnId");
            builder.HasOne<Square?>("enPassentTakablePawnOfBlack").WithOne().HasForeignKey<Position>("EnPassentTakableBlackPawnId");

            builder.Property("canWhiteCastleKingSide");
            builder.Property("canWhiteCastleQueenSide");
            builder.Property("canBlackCastleKingSide");
            builder.Property("canBlackCastleQueenSide");
            builder.Property("_50MovesRuleCounter");
            builder.Property<Guid>("Id").ValueGeneratedOnAdd();
            builder.HasKey("Id");
        }
    }
 public class SqaureTypeConfiguration : IEntityTypeConfiguration<Square>
    {
        public void Configure(EntityTypeBuilder<Square> builder)
        {
            builder.ToTable("Sqaures");
            builder.Property(s => s.File);
            builder.Property<Guid>("PositionId");
            builder.Property(s => s.Row);
            //this is FK to some other, irrelavant entity.
            builder.HasOne(s => s.Occupier).WithOne().IsRequired(false).HasForeignKey<Square>("OccupierId").IsRequired(false);
            builder.Property<Guid>("Id");
            builder.HasKey("Id");
            builder.Property<Guid?>("OccupierId").IsRequired(false);
        }
    }

问题是,在生成迁移时,迁移在Square 中为Position 创建了以下两个FK:

  public partial class ChangedBoard : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            /...
            migrationBuilder.AddColumn<Guid>(
                name: "PositionId1",
                table: "Sqaures",
                type: "uniqueidentifier",
                nullable: true);

            migrationBuilder.CreateIndex(
                name: "IX_Sqaures_PositionId1",
                table: "Sqaures",
                column: "PositionId1");


            migrationBuilder.AddForeignKey(
                name: "FK_Sqaures_Positions_PositionId1",
                table: "Sqaures",
                column: "PositionId1",
                principalTable: "Positions",
                principalColumn: "Id",
                onDelete: ReferentialAction.Restrict);
        }
      //there is also ```"PositionId" in other migration

我建议改为查看此图片,因为它更能说明问题:

平方表:

位置表:

如您所见,PositionId1PositionIdBoardId 都在其中,即使他们没有。 (只有PositionId 应该存在,作为一对多关系的一部分) 这引起了一些实际问题,因为在使用 EFCore 重试Position 实体时,我得到所有平方重复(两次)

这是为什么呢?我该如何解决?我尝试将显式类型和 FK 名称添加到一对多关系,更改配置顺序等。没有任何效果。

以下是实体:

正方形:

public record Square(Piece? Occupier, int Row, int File) : IComparable<Square>
    {
        private Square() : this(returnNull())
        {

        }

        private static Piece returnNull()
        {
            return null;
        }

        public int Row
        {
            get; set;
        } = Row;
        public int File
        {
            get; set;
        } = File;
        public Square(Piece? occupier) : this(occupier, 0, 0)
        {
            this.Occupier = occupier;
        }
        public void SetLocation(int row, int file)
        {
            Row = row;
            File = file;
        }

        public int CompareTo(Square? other)
        {
            if(other is null)
                return 1;
            int result = this.Row.CompareTo(other.Row);
            if(result != 0)
                return result;
            return this.File.CompareTo(other.File);
        }
}

职位:

    public record Position
    {
        private List<Square> squares;
        public List<Square> Squares
        {
            get
            {
                return squares;
            }
        }
        private int At(int row, int file)
        {
            return row * 8 + file;
        }
        private bool canWhiteCastleKingSide;
        private bool canWhiteCastleQueenSide;
        private bool canBlackCastleKingSide;
        private bool canBlackCastleQueenSide;
        private Square? enPassentTakablePawnOfWhite;
        private Square? enPassentTakablePawnOfBlack;
        private const int RowAmount = 8;
        private const int FileAmount = 8;

        private int _50MovesRuleCounter;
        private readonly IDictionary<PieceType, Func<int, int, IEnumerable<Ply>>> possibleMovesGenerationFunctionsDictinary;
//.....

编辑:当我尝试删除 Positionssquares 的整个类型配置(PK 配置除外)时,从正方形到位置的 FK 仍然存在(仅其中一个)

此外,当我尝试删除 Squares 公共属性时,在删除所有类型配置后,定位的 FK 确实消失了,但登机的 FK(根本没有被引用,也不应该被这里)仍然存在。将公共属性的名称从 Squares 更改为其他名称不会改变任何内容,我仍然需要这个属性,所以我不能删除它。将其标记为 [NoMapped] 会导致相同的结果

【问题讨论】:

    标签: c# sql-server entity-framework-core key one-to-many


    【解决方案1】:

    主要原因在这个代码块中

    public class PositionTypeConfiguration : IEntityTypeConfiguration<Position>
    {
         public void Configure(EntityTypeBuilder<Position> builder)
         {
              builder.ToTable("Positions");
              builder.Property<Guid>("BoardId");
              builder.Property<Guid?>("CurrentPositionId");
              builder.HasOne<Board>().WithMany("previosPositions").HasForeignKey("BoardId");
              builder.HasOne<Board>().WithOne("currentPosition").IsRequired(false).HasForeignKey<Position>("CurrentPositionId");
        
              builder.HasMany<Square>("squares").WithOne().HasForeignKey("PositionId");
              builder.Property<Guid?>("EnPassentTakableWhitePawnId");
              builder.Property<Guid?>("EnPassentTakableBlackPawnId");
              builder.HasOne<Square?>("enPassentTakablePawnOfWhite").WithOne().HasForeignKey<Position>("EnPassentTakableWhitePawnId");
              builder.HasOne<Square?>("enPassentTakablePawnOfBlack").WithOne().HasForeignKey<Position>("EnPassentTakableBlackPawnId");
        
              builder.Property("canWhiteCastleKingSide");
              builder.Property("canWhiteCastleQueenSide");
              builder.Property("canBlackCastleKingSide");
              builder.Property("canBlackCastleQueenSide");
              builder.Property("_50MovesRuleCounter");
              builder.Property<Guid>("Id").ValueGeneratedOnAdd();
              builder.HasKey("Id");
         }
    }
    

    您已经为 Square 添加了 ForeignKey,但我认为您可能没有注意到它区分大小写,因此您只需将“s”更改为“S”即可。

    builder.HasMany<Square>("Squares").WithOne().HasForeignKey("PositionId");
    

    这将使迁移机制意识到注册与public record Position处的public List&lt;Square&gt; Squares字段相同。它不会再次注册它。或者,您可以删除该行代码。

    问题是你想在private List&lt;Square&gt; squares上注册ForeignKey吗?还是只是个案问题?我不太明白你的设计是什么,所以我保留了原始形式的代码。

    我已使用您提供的代码进行迁移,但BoardId 字段未出现在Sqaures 表中。如果还是找不到原因,能否提供Square的其他型号配置?

    【讨论】:

      【解决方案2】:

      BoardId 已在您的 PositionTypeConfiguration 类中明确定义。如果您不需要它,只需从 PositionTypeConfiguration 中删除 builder.Property&lt;Guid&gt;("BoardId");

      现在关于PositionId1PositionId 问题。

      你使用这一行来创建一个外键

      builder.HasMany<Square>("squares").WithOne().HasForeignKey("PositionId");
      

      您的Position 类中的square 集合具有以下字段和属性

      private List<Square> squares;
      public List<Square> Squares
      {
          get
          {
              return squares;
          }
      }
      

      问题是您手动为私有字段创建外键,而 EF 自动为公共属性创建外键。因此,您配置了 2 个外键,因此配置了 2 个属性 PositionId1PositionId

      要解决此问题,请使用此行进行配置

      builder.HasMany<Square>(x => x.Squares).WithOne().HasForeignKey("PositionId");
      

      并删除私有字段,如果您希望它是不可变的,您可以使用私有集代替

      public class Position
      {
          public List<Square> Squares { get; private set; }
      }
      

      另外,我在Square 类上看不到PositionId 属性,如果没有它,您将无法使用您的模型将其保存到数据库中,因为您已将此列配置为不可为空。

      关于record 类型的最后一件事。

      EF 核心目前不支持记录类型的更改跟踪,因此如果您需要,最好使用常规类

      编辑:

      我在您发布的代码中没有看到任何可能导致BoardId 出现在Squares 表中的内容。但是您的项目中的其他一些配置可能会创建它。

      【讨论】:

        猜你喜欢
        • 2021-07-24
        • 2021-10-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多