我有一个典型的父母:孩子关系。课程如下:
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;被打破 - 第三个孩子看起来像第二个,等等。
我的问题是:
为什么这种不对称提供的代码几乎是&#34; copy-paste&#34;?
无论我做什么(在Child2
中更改OnModelCreating()
代码位置和/或在Child
中更改Child2
和Parent
属性顺序,{ {1}}以相同的方式生成,即Child2
中的FK。那么,是什么让EF选择Parent
超过Child2
进行此类生成?如果有三个或更多子项Child
仅按预期生成,则所有其余子项均为Child
。是什么让Child2
如此&#34;特殊&#34;如果我可以&#34;反向&#34;我很惊讶?
有什么想法吗? 感谢。
PS:代码中不允许使用明确的FK!
编辑:在Child
中注释掉virtual
以避免不对称。事实上,这与问题无关。
答案 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);
});