




Table Splitting是同一实体类的属性映射到共享主键的多个物理表,EF Core自动JOIN与同步写入;不同于一对多,它无独立子实体类,也不含外键约束,仅用于逻辑拆分宽表。
Table Splitting(表拆分)不是把一个实体拆成多个一对多关系,而是让 同一个实体类 的不同属性分别映射到 同一主键的多个物理表 上。数据库里这些表必须共享主键(且类型、约束完全一致),EF Core 会在查询时自动 JOIN,保存时同步写入。它和 Owned Entity 或 One-to-One 的关键区别在于:没有独立的子实体类,所有字段都属于同一个 CLR 类型。
典型场景是把大宽表按访问频率或安全策略拆分成主表(如 Users)和扩展表(如 UserProfiles),但业务代码仍只操作一个 User 对象。
必须在 OnModelCreating 中显式配置,不能靠约定。核心是调用 OwnsOne + ToTable,但注意:目标不是新建子实体,而是告诉 EF Core “这个导航属性对应另一个表,且主键复用”。
public 导航属性(类型为自身或另一个类),EF Core 会把它当作“拆分部分”OwnsOne 后必须链式调用 ToTable("TableName") 指定第二张表名Id),且不能在子表中重复声明主键 —— EF Core 会自动复用主表的主键作为外键约束示例(User 主表 + UserProfile

modelBuilder.Entity() .OwnsOne(u => u.Profile, nav => { nav.ToTable("UserProfiles"); nav.WithOwner(); // 表明 Profile 不拥有独立主键,复用 User.Id });
导航属性不能为 null(除非你手动处理空值逻辑),否则插入时会抛 InvalidOperationException:“The navigation property 'Profile' cannot be null.”
public User() { Profile = new UserProfile(); }
? 后缀(如 public UserProfile? Profile { get; set; }),否则 EF Core 会认为它可为空,导致生成的迁移包含外键约束,破坏 Table Splitting 语义Id)不能在两个实体类中都标记为 [Key];只在主实体类中标记,子类中去掉所有 Key/ForeignKey 特性EF Core 默认采用 INNER JOIN 加载拆分表,这意味着如果子表某行缺失,整个主实体查不到 —— 这和预期不符。解决方法是显式启用 IsRequired(false):
modelBuilder.Entity() .OwnsOne(u => u.Profile, nav => { nav.ToTable("UserProfiles"); nav.WithOwner().IsRequired(false); // 关键:允许子表记录不存在 });
Profile == null(前提是上面配了 IsRequired(false))ctx.UserProfiles.Where(...)),因为 UserProfile 不是独立 DbSet真正容易被忽略的是:Table Splitting 的“表”必须共用主键且无额外关联字段 —— 一旦你在子表里加了个 CreatedById 外键,它就不再是 Table Splitting,而该用 One-to-One 了。