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

EF Core中如何查询抽象类的所有子类对象并包含全部子字段?

问题描述

抽象基类定义:

public abstract class Workout
{
    public WorkoutType Type { get; set; }
    public List<Measurement> Measurements { get; set; } = new();

    // Id's and stuff
}

两个子类:

public class RunningWorkout : Workout
{
    public double PersonalBest { get; set; }
}
public class WeightWorkout : Workout
{
    public int PersonalBest { get; set; }
}

需要实现API端点返回用户所有训练记录,但目前只能拿到基类字段,试过继承相关方法但因为PersonalBest是原始类型没用,求解决办法。


可行解决方案

方案一:配置JSON序列化支持多态

如果用ASP.NET Core(默认用System.Text.Json),直接在Program.cs里配置多态映射,让序列化器识别子类并输出所有属性:

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0, new DefaultJsonTypeInfoResolver
        {
            Modifiers =
            {
                typeInfo =>
                {
                    if (typeInfo.Type == typeof(Workout))
                    {
                        typeInfo.PolymorphismOptions = new JsonPolymorphismOptions
                        {
                            DerivedTypes =
                            {
                                new JsonDerivedType(typeof(RunningWorkout), nameof(RunningWorkout)),
                                new JsonDerivedType(typeof(WeightWorkout), nameof(WeightWorkout))
                            },
                            // 可选:把类型鉴别器字段改成你想用的名字,默认是"$type"
                            TypeDiscriminatorPropertyName = "WorkoutType"
                        };
                    }
                }
            }
        });
    });

配置后API返回的JSON会自动包含子类的PersonalBest等属性,同时带上类型标识字段区分训练类型。

方案二:用DTO(数据传输对象)映射

创建对应每个子类的DTO,在API层把实体转成DTO返回,这种方式能完全控制返回字段,还能避免暴露实体细节:
先定义DTO类:

public abstract class WorkoutDto
{
    public WorkoutType Type { get; set; }
    public List<MeasurementDto> Measurements { get; set; } = new();
}

public class RunningWorkoutDto : WorkoutDto
{
    public double PersonalBest { get; set; }
}

public class WeightWorkoutDto : WorkoutDto
{
    public int PersonalBest { get; set; }
}

然后在API里做映射(手动或用AutoMapper都可以):

[HttpGet("user-workouts")]
public IActionResult GetUserWorkouts(int userId)
{
    var workouts = _dbContext.Workouts
        .Where(w => w.UserId == userId)
        .ToList(); // 前提是EF Core已经正确配置了继承映射,能加载子类数据

    var workoutDtos = workouts.Select(w => 
    {
        return w switch
        {
            RunningWorkout rw => new RunningWorkoutDto
            {
                Type = rw.Type,
                Measurements = rw.Measurements.Select(m => new MeasurementDto { /* 这里填Measurement到MeasurementDto的映射逻辑 */ }).ToList(),
                PersonalBest = rw.PersonalBest
            },
            WeightWorkout ww => new WeightWorkoutDto
            {
                Type = ww.Type,
                Measurements = ww.Measurements.Select(m => new MeasurementDto { /* 映射逻辑 */ }).ToList(),
                PersonalBest = ww.PersonalBest
            },
            _ => throw new NotSupportedException("不支持该训练类型")
        };
    }).ToList();

    return Ok(workoutDtos);
}

方案三:先确保EF Core正确加载子类数据

如果用EF Core,必须先配置继承映射(TPH/TPT/TPCC),否则查询基类时EF不会加载子类属性。比如用最常用的TPH(表每层次结构):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Workout>()
        .HasDiscriminator<WorkoutType>("WorkoutType")
        .HasValue<RunningWorkout>(WorkoutType.Running)
        .HasValue<WeightWorkout>(WorkoutType.Weight);
}

配置后查询_dbContext.Workouts时,EF会自动加载对应子类的所有属性,再配合方案一的JSON序列化配置就能返回完整数据。

方案四:动态类型(仅临时场景,不推荐生产用)

如果只是临时测试,可以用dynamic包装实体,但这种方式没有类型安全,生产环境别用:

[HttpGet("user-workouts")]
public IActionResult GetUserWorkouts(int userId)
{
    var workouts = _dbContext.Workouts
        .Where(w => w.UserId == userId)
        .ToList();

    var dynamicWorkouts = workouts.Select(w => (dynamic)w).ToList();
    return Ok(dynamicWorkouts);
}

内容的提问来源于stack exchange,提问作者Davrozz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 14:42:46