如何在EF TPH Code First中阻止创建冗余外键约束
这个问题我之前也碰到过——在TPH(表每层次)继承模式中,EF会默认把派生类的导航属性对应的外键约束应用到整个基类表上,哪怕另一个派生类根本不需要这个关联。就像你这里的情况,EstimateProjectPriceList不需要关联PriceList表,但EF还是给ProjectPriceList整张表加了PriceListId到PriceList的外键约束,导致插入EstimateProjectPriceList时因为PriceListId没有对应PriceList记录而报错。
下面给你两种可行的解决方案,优先推荐Fluent API的方式,因为它更灵活,适合处理TPH的复杂配置:
方法一:使用Fluent API(推荐)
在你的DbContext的OnModelCreating方法里,显式告诉EF只有BillingProjectPriceList需要和PriceList建立外键关联,基类的PriceListId只是复合主键的一部分,不需要全局外键约束。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { // 1. 配置基类的复合主键 modelBuilder.Entity<ProjectPriceList>() .HasKey(p => new { p.ProjectId, p.PriceListId, p.PriceListSourceType }); // 2. 显式配置TPH的鉴别器列(EF默认会生成,这里可以明确指定值) modelBuilder.Entity<ProjectPriceList>() .Map<BillingProjectPriceList>(m => m.Requires("Discriminator").HasValue("Billing")) .Map<EstimateProjectPriceList>(m => m.Requires("Discriminator").HasValue("Estimate")); // 3. 只为BillingProjectPriceList配置PriceList的外键关联 modelBuilder.Entity<BillingProjectPriceList>() .HasRequired(b => b.PriceList) // 如果允许PriceList为null,可以用HasOptional .WithMany() // 如果PriceList没有反向导航属性,就用空的WithMany() .HasForeignKey(b => b.PriceListId) .WillCascadeOnDelete(true); // 根据业务需求设置是否级联删除 // 基类不需要配置PriceList的外键,EF就不会生成全局的冗余约束了 }
这样配置后,EF生成的SQL就不会包含那张全局的FK_dbo.ProjectPriceList_dbo.PriceList_PriceListId约束,只会在BillingProjectPriceList对应的记录上通过EF层面验证PriceListId的有效性(因为TPH是单表,数据库层面没法区分,你可能需要在业务逻辑里确保EstimateProjectPriceList的PriceListId符合你的业务规则)。
方法二:调整实体结构(数据注解方式)
如果更倾向于数据注解,你需要稍微调整实体结构:把PriceListId从基类的复合主键中移除,仅在BillingProjectPriceList中定义PriceListId并作为外键。不过这种方法会改变你的主键结构,需要评估是否符合业务需求:
[Table("ProjectPriceList")] public abstract class ProjectPriceList { [Key, Column("ProjectId", Order = 0)] public int ProjectId { get; set; } [Key, Column("PriceListSourceType", Order = 1)] public int PriceListSourceType { get; set; } // 移除基类的PriceListId [ForeignKey("PriceListSourceType")] public virtual BillingProjectCode PriceListSourceTypeCode { get; set; } public int AddUsrnm { get; set; } public DateTime AddTmstmp { get; set; } [ForeignKey("ProjectId")] public virtual BillingProject BillingProject { get; set; } public virtual SnapShotPriceList SnapShotPriceList{ get; set; } } public class BillingProjectPriceList : ProjectPriceList { [Key, Column(Order = 2)] // 添加到复合主键 public int PriceListId { get; set; } [ForeignKey("PriceListId")] public virtual PriceList PriceList { get; set; } } public class EstimateProjectPriceList : ProjectPriceList { // 这里可以添加Estimate专属的标识字段,不需要关联PriceList [Key, Column(Order = 2)] public int EstimatePriceListId { get; set; } }
这种方式下,EF会根据派生类的主键配置生成对应的结构,不会生成冗余的外键约束,但缺点是改变了原本的复合主键设计,需要根据你的业务场景判断是否适用。
内容的提问来源于stack exchange,提问作者mangood

