You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 06:40:27