Room查询返回含List<Car>的自定义类型时遇字段读取错误
解决Room查询同时返回实体列表和聚合值的问题
问题原因
你遇到的错误核心是:Room无法将游标中的多行数据直接映射到包含List<Car>的POJO字段。Room的默认映射逻辑是逐行解析游标,将每一行数据转换为单个对象的字段,而你的ParameterQueryResult里的cars是列表类型,Room不知道该如何把多行数据打包成这个列表字段,所以抛出无法读取字段的错误。
解决方案
下面提供两种无需直接使用Cursor的可行方案:
方案1:拆分两次查询(最简单高效)
直接拆分查询逻辑,分别获取符合条件的Car列表和对应的平均引擎排量,再手动组合结果:
- 更新Dao接口:
@Dao interface CarDao { // 查询符合bodyType的所有Car @Query("SELECT * FROM Car WHERE bodyType=:bodyType") fun getCarsByBodyType(bodyType: BodyType): List<Car> // 查询该bodyType对应的平均引擎排量 @Query("SELECT AVG(engineCapacity) FROM Car WHERE bodyType=:bodyType") fun getAvgCapacityByBodyType(bodyType: BodyType): Double }
- 在Repository/ViewModel中组合结果:
fun getByBodyType(bodyType: BodyType): ParameterQueryResult { val cars = carDao.getCarsByBodyType(bodyType) val avgCapacity = if (cars.isEmpty()) 0.0 else carDao.getAvgCapacityByBodyType(bodyType) return ParameterQueryResult(cars, avgCapacity) }
方案2:单查询+窗口函数+中间类
如果希望通过一次数据库查询完成,可以使用SQL窗口函数OVER(PARTITION BY)让每一行结果都带上对应bodyType的平均排量,再在代码中提取聚合值并组装Car列表:
- 定义中间数据类(用于接收游标返回的完整行数据):
data class CarWithAvg( val id: Int, val brand: String, val bodyType: BodyType, val color: String, val engineCapacity: Double, val price: Double, val avgCapacity: Double )
- 更新Dao接口的查询语句:
@Dao interface CarDao { @Query("SELECT *, AVG(engineCapacity) OVER (PARTITION BY bodyType) as avgCapacity FROM Car WHERE bodyType=:bodyType") fun getCarsWithAvgByBodyType(bodyType: BodyType): List<CarWithAvg> }
- 转换为目标结果类型:
fun getByBodyType(bodyType: BodyType): ParameterQueryResult { val carWithAvgList = carDao.getCarsWithAvgByBodyType(bodyType) val cars = carWithAvgList.map { Car(it.id, it.brand, it.bodyType, it.color, it.engineCapacity, it.price) } // 提取唯一的平均排量(所有行的avgCapacity值相同) val avgCapacity = carWithAvgList.firstOrNull()?.avgCapacity ?: 0.0 return ParameterQueryResult(cars, avgCapacity) }
注意事项
- 方案1的两次查询在Room中会被优化(如果开启事务),性能上不会有明显问题,且代码逻辑最清晰。
- 方案2的窗口函数需要你的数据库版本支持(SQLite 3.25.0及以上,Android API 28+默认支持)。
内容的提问来源于stack exchange,提问作者Yasmin Ferguson
相关产品推荐
相关产品推荐

