EF TPT继承下抽象基类自引用配置及泛型转换报错如何解决
问题解决
问题背景
EF实体类结构
public abstract class CalendarItem { public Guid CalendarItemId { get; set; } public Guid? ParentCalendarItemId { get; set; } public string Name { get; set; } public string Description { get; set; } public virtual CalendarItem ParentCalendarItem { get; set; } public virtual ICollection<CalendarItem> ChildCalendarItems { get; set; } } public class CalendarSlot : CalendarItem { public bool IsAvailable { get; set; } } // 注意:此处原代码继承CalendarEvent应为笔误,需改为继承CalendarItem才可纳入继承体系 public class CalendarSession : CalendarItem { public DateTimeOffset StartDateTime { get; set; } public DateTimeOffset EndDateTime { get; set; } }
EF映射配置(原版本)
public class CalendarItemMap { public static void Map(ModelBuilder modelBuilder) { modelBuilder.Entity<CalendarItem>().ToTable("CalendarItem"); modelBuilder.Entity<CalendarItem>().HasKey(t => t.CalendarItemId); modelBuilder.Entity<CalendarItem>().HasOne(ci => ci.ParentCalendarItem) .WithMany(cs => cs.ChildCalendarItems).HasForeignKey(cs => cs.ParentCalendarItemId); } }
DbSet定义
public DbSet<CalendarItem> CalendarItems { get; set; } public DbSet<CalendarSession> CalendarSessions { get; set; } public DbSet<CalendarSlot> CalendarSlots { get; set; }
需求说明
- 基于现有继承关系配置基类自引用树形结构
CalendarSlot、CalendarSession两类实体都支持自引用- 抽象类
CalendarItem不可实例化 - 数据库层面树形结构存储在
CalendarItem表中,CalendarSlot、CalendarSession表与CalendarItem表为一对一关联
转换方法代码(原版本)
internal static TContract ToCalendarItem<TContract, TModel>(TModel calendarItemModel) where TContract : Contract.CalendarItem, new() where TModel : Model.CalendarItem { return new() { CalendarItemId = calendarItemModel.CalendarItemId, Name = calendarItemModel.Name, Description = calendarItemModel.Description, ParentCalendarItem = calendarItemModel.ParentCalendarItem != null ? ToCalendarItem<TContract, TModel>(calendarItemModel.ParentCalendarItem) : null }; }
报错信息
编译报错行:
ParentCalendarItem = calendarItemModel.ParentCalendarItem != null ? ToCalendarItem<TContract, TModel>(calendarItemModel.ParentCalendarItem) : null
错误内容:"Argument 1: Cannot convert from 'Model.CalendarItem' to TModel"
解决方案
一、EF继承与自引用配置修正
你需要用TPT(Table Per Type) 继承模式来满足需求,补充子类映射配置即可:
public class CalendarItemMap { public static void Map(ModelBuilder modelBuilder) { // 基类配置,抽象类EF会自动识别无法实例化 modelBuilder.Entity<CalendarItem>() .ToTable("CalendarItem") .HasKey(t => t.CalendarItemId); // 自引用配置,增加删除行为限制避免级联删除循环 modelBuilder.Entity<CalendarItem>() .HasOne(ci => ci.ParentCalendarItem) .WithMany(cs => cs.ChildCalendarItems) .HasForeignKey(cs => cs.ParentCalendarItemId) .OnDelete(DeleteBehavior.Restrict); // 补充子类TPT映射,实现和基类表一对一关联 modelBuilder.Entity<CalendarSlot>() .ToTable("CalendarSlot") .HasBaseType<CalendarItem>(); modelBuilder.Entity<CalendarSession>() .ToTable("CalendarSession") .HasBaseType<CalendarItem>(); } }
配置后自动满足:
- 基类是抽象类无法实例化
- 子类和基类表一对一关联,树形结构全部存在基类表
- 两个子类都继承了基类的自引用属性,支持各自的树形结构
二、转换方法报错修复
错误原因
ParentCalendarItem的声明类型是基类Model.CalendarItem,而泛型方法要求传入的参数是TModel(CalendarItem的子类),编译器无法确认父节点的运行时类型一定和当前节点的TModel类型一致,因此报类型转换错误。
修复方案
如果业务逻辑保证同类型节点的父节点一定是同类型,直接加显式转换即可,也可以增加类型判断避免运行时转换异常:
internal static TContract ToCalendarItem<TContract, TModel>(TModel calendarItemModel) where TContract : Contract.CalendarItem, new() where TModel : Model.CalendarItem { return new TContract() { CalendarItemId = calendarItemModel.CalendarItemId, Name = calendarItemModel.Name, Description = calendarItemModel.Description, ParentCalendarItem = calendarItemModel.ParentCalendarItem is TModel parent ? ToCalendarItem<TContract, TModel>(parent) : null }; }
内容的提问来源于stack exchange,提问作者Mikhail Ryzhinskiy
相关产品推荐
相关产品推荐

