首页 > 解决方案 > EF Core 类型配置添加冗余 FK

问题描述

我正在尝试配置 EFCore:Square和中的实体之间的关系Position。Position 与 square named 之间存在一对(位置)对多(squares)的关系squares,以及SquarenamedenPassentTakablePawnOfWhite和之间的两个一对一关系enPassentTakablePawnOfBlack。请注意,两个实体中的 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);
        }
    }

问题是,在迁移生成时,迁移创建了以下两个 FK 到Positionin Square

  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应该存在,作为一对多关系的一部分)这引起了一些实际问题,因为在Position使用 EFCore 重试实体时,我得到所有平方重复(两次)

这是为什么?我该如何解决?我尝试将显式类型和 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-serverentity-framework-corekeyone-to-many

解决方案


在您的课程BoardId中明确定义PositionTypeConfiguration。如果您不需要它,只需builder.Property<Guid>("BoardId");PositionTypeConfiguration.

现在关于PositionId1,PositionId问题。

您使用此行来创建外键

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

并且您在班级中具有以下square集合的字段和属性Position

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; }
}

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

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

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

编辑:

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


推荐阅读