You need to enable JavaScript to run this app. 如何使用Room映射得到含多层嵌套列表的CourseUiModel
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Room映射得到含多层嵌套列表的CourseUiModel

Room嵌套关联映射到UI模型的正确实现方式

报错核心原因

Room本身仅支持平铺列的自动映射,无法自动将多表JOIN产生的平铺行数据聚合为带集合嵌套的复杂结构。你直接将返回值声明为带lessonModels嵌套列表的CourseUiModel,Room既找不到对应列表的字段列,也不知道如何从重复的平铺行里做分组聚合,自然会抛出列缺失的错误。
另外你现有查询里三个表的同名字段(比如各表的Id、Name)没有加别名做区分,就算做平铺映射也会出现字段识别冲突。

推荐实现方案:基于Room原生@Relation的嵌套查询

这套方案不需要自己写复杂的JOIN聚合逻辑,性能和可维护性都远高于手写平铺查询。

对比三表INNER JOIN的写法,@Relation采用左关联逻辑,就算课程下没有课时、课时下没有主题,也不会出现数据丢失的问题,完全匹配嵌套结构的展示需求。

1. 先修正UI模型的缺失字段

你之前的CourseUiModel漏了Author字段,补上后避免映射时缺数据:

data class CourseUiModel(
    val Id: String,
    val Title: String,
    val Subtitle: String,
    val Author: String,
    val lessonModels: List<LessonUiModel>,
)

data class LessonUiModel(
    val Id: String,
    val Name: String,
    val topicModels: List<TopicUiModel>,
)

data class TopicUiModel(
    val Id: String,
    val Name: String
)

2. 定义Room专用的嵌套关联POJO

用Room提供的@Embedded和@Relation注解声明一对多的关联关系,不需要和UI模型完全复用:

// 第一层:课程关联其下所有课时
data class CourseWithLessons(
    @Embedded val course: Course,
    @Relation(
        entity = Lesson::class,
        parentColumn = "Id",
        entityColumn = "CourseId"
    )
    val lessons: List<LessonWithTopics>
)

// 第二层:课时关联其下所有主题
data class LessonWithTopics(
    @Embedded val lesson: Lesson,
    @Relation(
        entity = Topic::class,
        parentColumn = "Id",
        entityColumn = "LessonId"
    )
    val topics: List<Topic>
)

3. 调整DAO的查询逻辑

不需要自己写三表JOIN语句,Room会根据关联注解自动生成最优的单表关联查询,必须加@Transaction保证查询的事务一致性:

@Transaction
@Query("SELECT * FROM Course WHERE Id = :courseId")
fun getCourseWithStructure(courseId: String): LiveData<CourseWithLessons>

4. 增加POJO到UI模型的映射逻辑

把映射逻辑放在Repository或ViewModel层即可,不会污染Room的代码结构,同时可以把你之前SQL里的排序逻辑挪到映射层实现:

fun CourseWithLessons.toCourseUiModel(): CourseUiModel {
    return CourseUiModel(
        Id = course.Id,
        Title = course.Title,
        Subtitle = course.Subtitle,
        Author = course.Author,
        lessonModels = lessons
            // 按课时在课程中的顺序整理
            .sortedBy { it.lesson.OrderInCourse }
            .map { lessonWithTopics ->
                LessonUiModel(
                    Id = lessonWithTopics.lesson.Id,
                    Name = lessonWithTopics.lesson.Name,
                    topicModels = lessonWithTopics.topics
                        // 按主题在课时中的顺序整理
                        .sortedBy { it.OrderInLesson }
                        .map { topic ->
                            TopicUiModel(
                                Id = topic.Id,
                                Name = topic.Name
                            )
                        }
                )
            }
    )
}

不推荐的备选实现

如果你坚持要使用自己写的三表JOIN查询,需要先把查询结果改成平铺行POJO,所有同名字段加列别名避免冲突,再在业务层手动做两层分组聚合:

  • 先按课程ID聚合所有平铺行,提取课程基础信息
  • 再在同一个课程下按课时ID分组,提取每个课时的基础信息
  • 最后在每个课时下按主题ID分组,提取主题列表
    这套方案会产生大量笛卡尔积重复数据,分组逻辑代码冗余,容易出现排序错误、字段匹配错配的问题,维护成本远高于原生@Relation方案。

内容的提问来源于stack exchange,提问作者M. Akar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.26 13:06:03