EF Core:无鉴别器配置两个基类及其子类的一对一关系
问题分析与解决
实体类定义
[Table("BaseClassOne")] public class BaseClassOne { public Guid Id { get; set; } public BaseClassTwo BaseTwo { get; set; } } [Table("DerivedClassOne")] public class DerivedClassOne : BaseClassOne { public Guid Id { get; set; } public string Name { get; set; } } [Table("BaseClassTwo")] public class BaseClassTwo { public Guid Id { get; set; } public BaseClassOne BaseOne { get; set; } } [Table("DerivedClassTwo")] public class DerivedClassTwo : BaseClassTwo { public Guid Id { get; set; } }
当前ModelBuilder配置
builder .Entity<BaseClassOne>() .HasKey(b1 => b1.Id); builder .Entity<DerivedClassOne>() .HasBaseType<BaseClassOne>(); builder .Entity<BaseClassTwo>() .HasKey(b2 => b2.Id); builder .Entity<DerivedClassTwo>() .HasBaseType<BaseClassTwo>(); // 问题触发点 builder .Entity<BaseClassTwo>() .HasOne(b2 => b2.BaseOne) .WithOne(b1 => b1.BaseTwo) .HasForeignKey<BaseClassTwo>(e => e.Id) .OnDelete(Rule.Cascade);
触发错误的查询
_context.BaseClassesTwo .Select(b2 => new { val1 = b2.name, val2 = b2.BaseClassOne.name }) .ToList();
错误信息:找不到m.Discriminator列
原因分析
你采用的是EF的TPT(Table-Per-Type)继承模式(每个类都标记了[Table]特性),但EF默认会为该模式生成鉴别器列,除非明确禁用。同时你的代码存在两处关键问题:
- 子类重复定义
Id属性,TPT模式下主键由基类维护,重复声明会导致映射冲突。 - 一对一关系直接使用主键作为外键,EF会默认触发鉴别器生成逻辑,但数据库中并未创建该列,从而抛出错误。
解决方案
1. 修正实体类定义
删除子类中重复的Id属性,TPT模式下主键继承自基类即可:
[Table("DerivedClassOne")] public class DerivedClassOne : BaseClassOne { public string Name { get; set; } } [Table("DerivedClassTwo")] public class DerivedClassTwo : BaseClassTwo { // 移除重复的Id属性 }
2. 禁用鉴别器并修正关系配置
在ModelBuilder中明确禁用TPT模式的鉴别器,同时调整一对一关系的外键配置:
方案一:使用独立外键(推荐)
先修改实体类添加外键属性:
public class BaseClassTwo { public Guid Id { get; set; } public Guid BaseClassOneId { get; set; } // 新增外键属性 public BaseClassOne BaseOne { get; set; } }
再更新配置代码:
builder.Entity<BaseClassOne>() .HasKey(b1 => b1.Id) .ToTable("BaseClassOne"); builder.Entity<DerivedClassOne>() .HasBaseType<BaseClassOne>() .ToTable("DerivedClassOne") .HasNoDiscriminator(); // 禁用鉴别器 builder.Entity<BaseClassTwo>() .HasKey(b2 => b2.Id) .ToTable("BaseClassTwo"); builder.Entity<DerivedClassTwo>() .HasBaseType<BaseClassTwo>() .ToTable("DerivedClassTwo") .HasNoDiscriminator(); // 禁用鉴别器 // 配置一对一关系 builder.Entity<BaseClassTwo>() .HasOne(b2 => b2.BaseOne) .WithOne(b1 => b1.BaseTwo) .HasForeignKey<BaseClassTwo>(b2 => b2.BaseClassOneId) .OnDelete(DeleteBehavior.Cascade);
方案二:保留主键作为外键(共享主键关联)
若必须用主键作为外键,需确保配置时明确禁用鉴别器:
builder.Entity<BaseClassOne>() .HasKey(b1 => b1.Id) .ToTable("BaseClassOne"); builder.Entity<DerivedClassOne>() .HasBaseType<BaseClassOne>() .ToTable("DerivedClassOne") .HasNoDiscriminator(); builder.Entity<BaseClassTwo>() .HasKey(b2 => b2.Id) .ToTable("BaseClassTwo"); builder.Entity<DerivedClassTwo>() .HasBaseType<BaseClassTwo>() .ToTable("DerivedClassTwo") .HasNoDiscriminator(); // 共享主键的一对一配置 builder.Entity<BaseClassTwo>() .HasOne(b2 => b2.BaseOne) .WithOne(b1 => b1.BaseTwo) .HasForeignKey<BaseClassTwo>(b2 => b2.Id) .OnDelete(DeleteBehavior.Cascade);
3. 修正查询中的属性错误
原查询中b2.name和b2.BaseClassOne.name均为无效属性,需调整为子类的Name属性,同时若要查询子类数据,需使用OfType筛选:
_context.BaseClassesTwo .OfType<DerivedClassTwo>() .Select(b2 => new { val1 = ((DerivedClassOne)b2.BaseOne).Name, val2 = b2.BaseOne is DerivedClassOne dc1 ? dc1.Name : null }) .ToList();
内容的提问来源于stack exchange,提问作者SNO
相关产品推荐
相关产品推荐

