EF Core 2 1-1关系意外行为

时间:2017-10-31 04:15:26

标签: ef-migrations one-to-one ef-core-2.0

我有一个典型的父母:孩子关系。课程如下:

public class Parent
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public Child Child { get; set; }

}

public class Child
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public Parent Parent { get; set; }
}

模型也很简单:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Child>().HasKey(p => p.Id);
        modelBuilder.Entity<Child>().Property(p => p.Name).HasMaxLength(256);
        modelBuilder.Entity<Child>().HasOne(p => p.Parent).WithOne(p => p.Child).IsRequired().OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<Parent>().HasKey(p => p.Id);
        modelBuilder.Entity<Parent>().Property(p => p.Name).HasMaxLength(256).IsRequired();
    }

迁移看起来像这样:

protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Parent",
            columns: table => new
            {
                Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Parent", x => x.Id);
            });

        migrationBuilder.CreateTable(
            name: "Child",
            columns: table => new
            {
                Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
                ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Child", x => x.Id);
                table.ForeignKey(
                    name: "FK_Child_Parent_ParentId",
                    column: x => x.ParentId,
                    principalTable: "Parent",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

        migrationBuilder.CreateIndex(
            name: "IX_Child_ParentId",
            table: "Child",
            column: "ParentId",
            unique: true);
    }

这几乎是预期的。 FK在&#34; Child&#34;表

但是,如果我添加一个新的孩子,事情就会出乎意料地发生变化。这是第二个孩子和修改后的父母:

public class Child2
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public /*virtual */Parent Parent { get; set; }
}

public class Parent
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public Child Child { get; set; }

    public Child2 Child2 { get; set; }
}

同样,该模型没有什么特别之处:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Child>().HasKey(p => p.Id);
        modelBuilder.Entity<Child>().Property(p => p.Name).HasMaxLength(256);
        modelBuilder.Entity<Child>().HasOne(p => p.Parent).WithOne(p => p.Child).IsRequired().OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<Child2>().HasKey(p => p.Id);
        modelBuilder.Entity<Child2>().Property(p => p.Name).HasMaxLength(256);
        modelBuilder.Entity<Child2>().HasOne(p => p.Parent).WithOne(p => p.Child2).IsRequired().OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<Parent>().HasKey(p => p.Id);
        modelBuilder.Entity<Parent>().Property(p => p.Name).HasMaxLength(256).IsRequired();
    }

现在惊喜。新迁移:

protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Child2",
            columns: table => new
            {
                Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Child2", x => x.Id);
            });

        migrationBuilder.CreateTable(
            name: "Parent",
            columns: table => new
            {
                Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                Child2Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Parent", x => x.Id);
                table.ForeignKey(
                    name: "FK_Parent_Child2_Child2Id",
                    column: x => x.Child2Id,
                    principalTable: "Child2",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

        migrationBuilder.CreateTable(
            name: "Child",
            columns: table => new
            {
                Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
                ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Child", x => x.Id);
                table.ForeignKey(
                    name: "FK_Child_Parent_ParentId",
                    column: x => x.ParentId,
                    principalTable: "Parent",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

        migrationBuilder.CreateIndex(
            name: "IX_Child_ParentId",
            table: "Child",
            column: "ParentId",
            unique: true);

        migrationBuilder.CreateIndex(
            name: "IX_Parent_Child2Id",
            table: "Parent",
            column: "Child2Id",
            unique: true);
    }

人们很容易发现

  

Child2Id

父表中的FK,而Child表没有这样的键。此外,还有另一个惊喜 - 失踪

  

的ParentId

Child2表中的FK。

事情似乎是非常不对称的,看起来更像是一个1:1的关系链,而不是有两个孩子的父母。 但是,如果我添加另一个孩子(代码未粘贴以避免膨胀)&#34;链&#34;被打破 - 第三个孩子看起来像第二个,等等。

我的问题是:

  1. 为什么这种不对称提供的代码几乎是&#34; copy-paste&#34;?

  2. 无论我做什么(在Child2中更改OnModelCreating()代码位置和/或在Child中更改Child2Parent属性顺序,{ {1}}以相同的方式生成,即Child2中的FK。那么,是什么让EF选择Parent超过Child2进行此类生成?如果有三个或更多子项Child仅按预期生成,则所有其余子项均为Child。是什么让Child2如此&#34;特殊&#34;如果我可以&#34;反向&#34;我很惊讶?

  3. 有什么想法吗? 感谢。

    PS:代码中不允许使用明确的FK!

    编辑:在Child中注释掉virtual以避免不对称。事实上,这与问题无关。

1 个答案:

答案 0 :(得分:3)

答案基本上包含在Relationships文档的其他关系模式 - 一对一部分中的以下注意中:

  

EF将根据其检测外键属性的能力选择其中一个实体作为依赖实体。如果选择了错误的实体作为依赖实体,则可以使用Fluent API来纠正此问题。

EF与一对多关系没有问题,因为许多方面始终是依赖关系。对于具有显式FK的一对一关系也没有问题,因为具有FK的一侧是从属的。

但是对于没有明确FK的一对一关系,在你的情况下,它不清楚依赖哪一方,因此选择是随机的(它应该是一个例外),因此是不可靠的。 / p>

根据经验,始终使用HasForeignKey流畅的API使用显式/阴影属性的相应重载显式指定FK(因此是依赖实体):

modelBuilder.Entity<Child>()
    .HasOne(p => p.Parent)
    .WithOne(p => p.Child)
    .HasForeignKey<Child>("ParentId")
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

modelBuilder.Entity<Child2>()
    .HasOne(p => p.Parent)
    .WithOne(p => p.Child2)
    .HasForeignKey<Child2>("ParentId")
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

生成的迁移是:

migrationBuilder.CreateTable(
    name: "Parent",
    columns: table => new
    {
        Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
        Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Parent", x => x.Id);
    });

migrationBuilder.CreateTable(
    name: "Child",
    columns: table => new
    {
        Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
        Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
        ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Child", x => x.Id);
        table.ForeignKey(
            name: "FK_Child_Parent_ParentId",
            column: x => x.ParentId,
            principalTable: "Parent",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    });

migrationBuilder.CreateTable(
    name: "Child2",
    columns: table => new
    {
        Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
        Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
        ParentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Child2", x => x.Id);
        table.ForeignKey(
            name: "FK_Child2_Parent_ParentId",
            column: x => x.ParentId,
            principalTable: "Parent",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    });