如何使用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
相关产品推荐
相关产品推荐

