EF Core 6生成DbContext脚本时基类与继承类出现重复外键求助
问题描述
我使用EF Core采用**Table Per Type(TPT)**继承模式设计数据库:
BaseTypes表使用Id和ShopCode作为复合主键,通过ShopCode列创建了名为FK_BaseType_Shops的外键关联到Shops表。- 继承自
BaseType的ATypes和BTypes表同样使用Id和ShopCode作为复合主键,按设计它们不应直接拥有指向Shops表的外键(关联应通过继承关系从基类传递)。
但通过Script-DbContext生成SQL脚本时,发现BaseTypes、ATypes和BTypes表都被生成了同名的FK_BaseType_Shops外键约束。作为EF Core模型优先的新手,想确认这是配置遗漏还是EF Core的Bug?
模型代码
public abstract class BaseType { public string Id { get; set; } public string ShopCode { get; set; } = null!; public string Name { get; set; } = null!; public virtual Shop Shop { get; set; } = null!; } public partial class AType : BaseType { public string? Property1 { get; set; } } public partial class BType : BaseType { public string? Property2 { get; set; } } public partial class Shop { public string ShopCode { get; set; } = null!; public string ShopName { get; set; } = null!; }
DbContext配置代码
public partial class myContext : DbContext { private string _connectionString = "Server=(localdb)\\mssqllocaldb;Database=TestDB;Trusted_Connection=True"; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer(_connectionString); } } public virtual DbSet<BaseType> BaseTypes { get; set; } = null!; public virtual DbSet<AType> ATypes { get; set; } = null!; public virtual DbSet<BType> BTypes { get; set; } = null!; public virtual DbSet<Shop> Shops { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { try { modelBuilder.Entity<AType>(entity => { entity.ToTable("ATypes").HasBaseType<BaseType>(); }); modelBuilder.Entity<BaseType>(entity => { entity.ToTable("BaseTypes").HasKey(e => new{e.Id, e.ShopCode}); entity.HasOne(e => e.Shop) .WithMany() .HasConstraintName("FK_BaseType_Shops") .OnDelete(DeleteBehavior.NoAction); }); modelBuilder.Entity<BType>(entity => { entity.ToTable("BTypes").HasBaseType<BaseType>(); }); modelBuilder.Entity<Shop>(entity => { entity.HasKey(e => e.ShopCode); }); } catch { throw; } OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); }
生成的SQL脚本
CREATE TABLE [Shops] ( [ShopCode] nvarchar(450) NOT NULL, [ShopName] nvarchar(max) NOT NULL, CONSTRAINT [PK_Shops] PRIMARY KEY ([ShopCode]) ); GO CREATE TABLE [BaseTypes] ( [Id] nvarchar(450) NOT NULL, [ShopCode] nvarchar(450) NOT NULL, [Name] nvarchar(max) NOT NULL, CONSTRAINT [PK_BaseTypes] PRIMARY KEY ([Id], [ShopCode]), CONSTRAINT [FK_BaseType_Shops] FOREIGN KEY ([ShopCode]) REFERENCES [Shops] ([ShopCode]) ); GO CREATE TABLE [ATypes] ( [Id] nvarchar(450) NOT NULL, [ShopCode] nvarchar(450) NOT NULL, [Property1] nvarchar(max) NULL, CONSTRAINT [PK_ATypes] PRIMARY KEY ([Id], [ShopCode]), CONSTRAINT [FK_ATypes_BaseTypes_Id_ShopCode] FOREIGN KEY ([Id], [ShopCode]) REFERENCES [BaseTypes] ([Id], [ShopCode]), CONSTRAINT [FK_BaseType_Shops] FOREIGN KEY ([ShopCode]) REFERENCES [Shops] ([ShopCode]) ); GO CREATE TABLE [BTypes] ( [Id] nvarchar(450) NOT NULL, [ShopCode] nvarchar(450) NOT NULL, [Property2] nvarchar(max) NULL, CONSTRAINT [PK_BTypes] PRIMARY KEY ([Id], [ShopCode]), CONSTRAINT [FK_BaseType_Shops] FOREIGN KEY ([ShopCode]) REFERENCES [Shops] ([ShopCode]), CONSTRAINT [FK_BTypes_BaseTypes_Id_ShopCode] FOREIGN KEY ([Id], [ShopCode]) REFERENCES [BaseTypes] ([Id], [ShopCode]) ); GO CREATE INDEX [IX_ATypes_ShopCode] ON [ATypes] ([ShopCode]); GO CREATE INDEX [IX_BaseTypes_ShopCode] ON [BaseTypes] ([ShopCode]); GO CREATE INDEX [IX_BTypes_ShopCode] ON [BTypes] ([ShopCode]); GO
问题分析与解决
原因说明
这不是EF Core的Bug,而是默认行为导致的:EF Core在TPT继承模式下,会将基类中定义的导航属性(这里的Shop)视为每个子类实体的一部分,因此会自动为子类表生成对应的外键约束,即使子类已经通过复合主键关联到基类。
解决方法
在子类的实体配置中,显式忽略Shop导航属性,告诉EF Core不要为子类表生成指向Shops的外键约束。修改OnModelCreating中的子类配置代码即可:
修改后的AType和BType配置:
modelBuilder.Entity<AType>(entity => { entity.ToTable("ATypes").HasBaseType<BaseType>(); // 忽略Shop导航属性,避免生成额外外键 entity.Ignore(e => e.Shop); }); modelBuilder.Entity<BType>(entity => { entity.ToTable("BTypes").HasBaseType<BaseType>(); // 忽略Shop导航属性,避免生成额外外键 entity.Ignore(e => e.Shop); });
验证结果
修改后重新生成SQL脚本,ATypes和BTypes表将不再包含FK_BaseType_Shops外键约束,仅保留与BaseTypes表的关联外键,符合设计预期。
内容的提问来源于stack exchange,提问作者Jeremy Deters
相关产品推荐
相关产品推荐

