EF Core TPT为何同时生成基类与派生类的外键?
我有一个模型,其中Company和NaturalPerson均继承自Person(公司属于法人)。公司和自然人都可以拥有资产,因此我创建了Ownership对象来关联Person,该Person可以是Company或NaturalPerson。
使用TPT生成模型时,已生成Person、Company、NaturalPerson表,这部分正常。但Ownership表中,我预期仅存在指向Person表的1个外键,却额外生成了指向Company和NaturalPerson表的2个外键,总计3个。请问这是否正常?若正常,原因是什么?还是我配置出错了?
代码示例
public class MyContext : DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Person>().ToTable("Person"); modelBuilder.Entity<NaturalPerson>().ToTable("NaturalPerson"); modelBuilder.Entity<LegalPerson>().ToTable("LegalPerson"); modelBuilder.Entity<CompanyThatCannotOwnStuff>().ToTable("CompanyThatCannotOwnStuff"); modelBuilder.Entity<Company>().ToTable("Company"); modelBuilder.Entity<SharableGood>().ToTable("Good"); modelBuilder.Entity<Ownership>() .HasOne(o => o.Owner) .WithMany() .HasForeignKey(o => o.OwnerId) .OnDelete(DeleteBehavior.NoAction); modelBuilder.Entity<Ownership>() .HasOne(o => o.SharableGood) .WithMany() .HasForeignKey(o => o.SharableGoodId) .OnDelete(DeleteBehavior.NoAction); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("Server=localhost;Database=Test;Trusted_Connection=True;"); } } public abstract class Person { public Guid Id { get; set; } } public class NaturalPerson : Person { public List<Ownership> Ownerships { get; set; } } public class LegalPerson : Person { } public class CompanyThatCannotOwnStuff : LegalPerson { } public class Company: LegalPerson { public List<Ownership> Ownerships { get; set; } } public class SharableGood { public Guid Id { get; set; } } public class Ownership { public Guid Id { get; set; } public Guid SharableGoodId { get; set; } public SharableGood SharableGood { get; set; } public Guid OwnerId { get; set; } public Person Owner { get; set; } public DateTime ImportantDate { get; set; } }
生成的Ownership表迁移代码
migrationBuilder.CreateTable( name: "Ownership", columns: table => new { Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), SharableGoodId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), OwnerId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), ImportantDate = table.Column<DateTime>(type: "datetime2", nullable: false), CompanyId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), NaturalPersonId = table.Column<Guid>(type: "uniqueidentifier", nullable: true) }, constraints: table => { table.PrimaryKey("PK_Ownership", x => x.Id); table.ForeignKey( name: "FK_Ownership_Company_CompanyId", column: x => x.CompanyId, principalTable: "Company", principalColumn: "Id"); table.ForeignKey( name: "FK_Ownership_Good_SharableGoodId", column: x => x.SharableGoodId, principalTable: "Good", principalColumn: "Id"); table.ForeignKey( name: "FK_Ownership_NaturalPerson_NaturalPersonId", column: x => x.NaturalPersonId, principalTable: "NaturalPerson", principalColumn: "Id"); table.ForeignKey( name: "FK_Ownership_Person_OwnerId", column: x => x.OwnerId, principalTable: "Person", principalColumn: "Id"); });
解答
这是配置错误导致的,核心原因是派生类的导航属性未与Ownership.Owner正确关联,EF Core误生成了额外的独立关联。
问题根源
你的NaturalPerson和Company类中都定义了List<Ownership>导航属性,但在OnModelCreating中,你仅配置了Ownership.Owner到Person的关联(且WithMany()未指定对应导航属性),EF Core会默认将派生类中的Ownerships视为全新的、独立的关联关系,因此为每个派生类生成了单独的外键列(CompanyId、NaturalPersonId),再加上你手动配置的OwnerId,最终出现三个外键。
修复方案
修改OnModelCreating,明确派生类的Ownerships导航属性与Ownership.Owner的关联:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Person>().ToTable("Person"); modelBuilder.Entity<NaturalPerson>().ToTable("NaturalPerson"); modelBuilder.Entity<LegalPerson>().ToTable("LegalPerson"); modelBuilder.Entity<CompanyThatCannotOwnStuff>().ToTable("CompanyThatCannotOwnStuff"); modelBuilder.Entity<Company>().ToTable("Company"); modelBuilder.Entity<SharableGood>().ToTable("Good"); // 配置NaturalPerson与Ownership的关联 modelBuilder.Entity<NaturalPerson>() .HasMany(n => n.Ownerships) .WithOne(o => o.Owner) .HasForeignKey(o => o.OwnerId) .OnDelete(DeleteBehavior.NoAction); // 配置Company与Ownership的关联 modelBuilder.Entity<Company>() .HasMany(c => c.Ownerships) .WithOne(o => o.Owner) .HasForeignKey(o => o.OwnerId) .OnDelete(DeleteBehavior.NoAction); // 保留SharableGood的关联配置 modelBuilder.Entity<Ownership>() .HasOne(o => o.SharableGood) .WithMany() .HasForeignKey(o => o.SharableGoodId) .OnDelete(DeleteBehavior.NoAction); }
这样配置后,EF Core会将所有派生类的Ownerships导航属性映射到同一个OwnerId外键,不会再生成多余的CompanyId和NaturalPersonId列。
内容的提问来源于stack exchange,提问作者Seb

