EF Core 类型配置添加冗余 FK

EF Core type configuration adds redundant FKs

我正在尝试配置 EFCore 中实体之间的关系:SquarePosition。 Position 与名为 squares 的 square 具有一对一的关系,以及名为 enPassentTakablePawnOfWhiteenPassentTakablePawnOfBlackSquare 之间的两个一对一关系。请注意,两个实体中的 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

我建议改为查看此图片,因为它说明得更好:

方块table:

位置table:

如您所见,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 public 属性 时,在删除所有类型配置后,FK to position 确实消失了,但是 FK to board (根本没有被引用,也不应该在这里)仍然存在。将 public 属性 的名称从 Squares 更改为其他内容并没有任何改变,我仍然需要这个 属性 所以我不能删除它。将其标记为 [NoMapped] 会导致相同的结果

你的PositionTypeConfigurationclass明确定义了BoardId。如果您不需要它,只需从 PositionTypeConfiguration.

中删除 builder.Property<Guid>("BoardId");

现在关于 PositionId1PositionId 问题。

你用这一行创建外键

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

并且您的 Position class

中的 square 集合有以下字段和 属性
private List<Square> squares;
public List<Square> Squares
{
    get
    {
        return squares;
    }
}

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

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

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

并删除私有字段,如果您希望它是 immutable

,则可以改用私有集
public class Position
{
    public List<Square> Squares { get; private set; }
}

我也没有在 Square class 上看到 PositionId 属性,没有它你将无法使用你的模型,因为您已将此列配置为不可为空。

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

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

编辑:

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

主要原因在这个代码块

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 添加了外键,但我认为您可能没有注意到它区分大小写,因此您只需将“s”更改为“S”即可。

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

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

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

我使用了您提供的代码进行迁移,但是BoardId字段没有出现在Sqaurestable中。如果还是找不到原因,能否提供一下Square?

的其他机型配置