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

C#如何在DTO中合并多个不同类型列表为统一列表传递给前端

实现方案

一、DTO层设计

方案1:多态继承方案(类型安全,无冗余字段)

先定义资产类型枚举和公共基类:

// 资产类型枚举,用于前端快速识别资源类型
public enum LessonAssetType
{
    Image = 1,
    InstructionCard = 2,
    // 后续新增类型直接在这里加枚举值即可
    Video = 3,
    Audio = 4
}

// 公共资产基类,所有资源类型继承此类
public abstract class LessonAssetBase
{
    public long Id { get; set; }
    public LessonAssetType AssetType { get; protected set; }
    public int SortOrder { get; set; } // 排序字段,控制前端展示顺序
}

修改原有资产类继承基类:

public class LessonImageAsset : LessonAssetBase
{
    public string Title { get; set; }
    public ImageDetailView Image { get; set; }

    public LessonImageAsset()
    {
        AssetType = LessonAssetType.Image;
        Image = new ImageDetailView();
    }
}

public class LessonInstructionCardAsset : LessonAssetBase
{
    public string Instructions { get; set; }

    public LessonInstructionCardAsset()
    {
        AssetType = LessonAssetType.InstructionCard;
    }
}

最后修改返回的根DTO:

public class LessonDetailView : BaseResult
{
    public long Id { get; set; }
    public string Title { get; set; }
    // 统一的资产列表,所有类型资源都放在这里
    public List<LessonAssetBase> Assets { get; set; } = new();
}

注意:如果使用System.Text.Json序列化,需要配置多态序列化支持,示例配置如下:

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0,
            AppJsonSerializerContext.Default);
    });

[JsonDerivedType(typeof(LessonImageAsset), typeDiscriminator: "image")]
[JsonDerivedType(typeof(LessonInstructionCardAsset), typeDiscriminator: "instructionCard")]
public abstract class LessonAssetBase { // 原有属性 }

[JsonSerializable(typeof(LessonAssetBase))]
[JsonSerializable(typeof(LessonImageAsset))]
[JsonSerializable(typeof(LessonInstructionCardAsset))]
public partial class AppJsonSerializerContext : JsonSerializerContext
{
}

方案2:平层统一DTO方案(序列化简单,前端适配成本低)

直接定义一个包含所有可能字段的统一资产类,通过类型字段标识当前资源类型:

public enum LessonAssetType
{
    Image = 1,
    InstructionCard = 2,
    Video = 3
}

public class LessonAssetsListView
{
    public long Id { get; set; }
    public LessonAssetType AssetType { get; set; }
    public int SortOrder { get; set; }
    // 图片类型专属字段
    public string Title { get; set; }
    public ImageDetailView Image { get; set; }
    // 操作指引卡专属字段
    public string Instructions { get; set; }
    // 后续新增类型的字段直接在这里追加即可
}

// 根DTO修改为
public class LessonDetailView : BaseResult
{
    public long Id { get; set; }
    public string Title { get; set; }
    public List<LessonAssetsListView> Assets { get; set; } = new();
}
  • 优点:无需处理多态序列化配置,前端无需判断不同结构,直接根据AssetType读取对应字段即可
  • 缺点:资产类型越多,冗余字段越多,不适合类型字段差异极大的场景

二、合并逻辑的实现位置

合并、排序逻辑完全放在Service层实现,DTO层只做数据载体,不包含任何业务逻辑:

// Service层示例代码
public async Task<LessonDetailView> GetLessonDetail(long lessonId)
{
    var lesson = await _dbContext.Lessons.FindAsync(lessonId);
    // 分别查询各类资产
    var images = await _dbContext.LessonImages
        .Where(x => x.LessonId == lessonId)
        .Select(x => new LessonImageAsset
        {
            Id = x.Id,
            Title = x.Title,
            Image = _mapper.Map<ImageDetailView>(x.Image),
            SortOrder = x.Sort
        })
        .ToListAsync();
    
    var cards = await _dbContext.LessonInstructionCards
        .Where(x => x.LessonId == lessonId)
        .Select(x => new LessonInstructionCardAsset
        {
            Id = x.Id,
            Instructions = x.Instructions,
            SortOrder = x.Sort
        })
        .ToListAsync();

    // 合并所有资产并按排序字段排序
    var allAssets = new List<LessonAssetBase>();
    allAssets.AddRange(images);
    allAssets.AddRange(cards);
    // 后续新增资产类型只需要在此处添加查询和合并逻辑即可
    allAssets = allAssets.OrderBy(x => x.SortOrder).ToList();

    return new LessonDetailView
    {
        Id = lesson.Id,
        Title = lesson.Title,
        Assets = allAssets
    };
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 07:33:02