![](/img/trans.png)
[英]Android Room Relations get limited fields from embedded model
[英]“no such column” SQL error from Android Room and multiple @Embedded fields on POJO
最初的问题在最底层。 我为我的问题创建了一个最小的(非工作性)示例,希望可以更轻松地阅读。 该示例在gitlab上 。 有描述该问题的自述文件。 我在这里粘贴项目的某些部分。
数据模型非常简单:
Owner <--(1:N)-- Child --(N:1)--> ReferencedByChild
我要做的就是从数据库中读取一个Owner
及其所有关联的Child
对象,并且对于每个Child
对象,还要读取它引用的ReferencedByChild
对象。
重现我的问题的整个代码如下。 什么我不是100%肯定是@Relation
上OwnerWithEverything
POJO。 请参阅下面。
@Database(
entities = [
Owner::class,
Child::class,
ReferencedByChild::class
],
version = 1
)
abstract class AppDatabase : RoomDatabase() {
abstract fun appDao(): AppDao
}
@Dao
abstract class AppDao {
@Insert
abstract fun insertOwner(owner: Owner): Long
@Insert
abstract fun insertChild(child: Child): Long
@Insert
abstract fun insertReferencedByChild(referencedByChild: ReferencedByChild): Long
@Query("SELECT * FROM Child INNER JOIN ReferencedByChild ON Child.referencedByChildId = ReferencedByChild.refByChildId ORDER BY Child.childText")
abstract fun findAllChildrenWithReferencedClasses(): List<ChildWithReferenced>
// Commenting this query out makes the build pass, so something here is incorrect.
@Query("SELECT * FROM Owner")
abstract fun findOwnersWithEverything(): List<OwnerWithEverything>
}
// ENTITIES
@Entity
data class Owner(
@PrimaryKey(autoGenerate = true)
val ownerId: Long,
val ownerText: String
)
@Entity(
foreignKeys = [
ForeignKey(
entity = Owner::class,
parentColumns = arrayOf("ownerId"),
childColumns = arrayOf("referencedOwnerId"),
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = ReferencedByChild::class,
parentColumns = arrayOf("refByChildId"),
childColumns = arrayOf("referencedByChildId"),
onDelete = ForeignKey.CASCADE
)
]
)
data class Child(
@PrimaryKey(autoGenerate = true)
val childId: Long,
val childText: String,
val referencedOwnerId: Long,
val referencedByChildId: Long
)
@Entity
data class ReferencedByChild(
@PrimaryKey(autoGenerate = true)
val refByChildId: Long,
val refText: String
)
// POJOS
// The Child has exactly one ReferencedByChild reference. This POJO joins those two
class ChildWithReferenced(
@Embedded
var child: Child,
@Embedded
var referencedByChild: ReferencedByChild
)
class OwnerWithEverything {
@Embedded
var owner: Owner? = null
@Relation(
parentColumn = "ownerId",
entityColumn = "referencedOwnerId",
entity = Child::class // which entity should be defined here?
)
var childrenWithReferenced: List<ChildWithReferenced>? = null
}
构建此代码将导致以下错误消息:
error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: refByChildId)
我认为Owner
查询的构造错误,但是我不确定。 如果那是问题,那么构造查询的正确方法是什么?
这是最初的问题,我有一个嵌套的POJO结构,该结构应表示一个具有多个Rounds
Game
,每个Round
都有一个与之关联的Topic
:
class GameWithRounds {
@Embedded
var game: Game? = null
@Relation(
parentColumn = "id",
entityColumn = "gameId",
entity = RoundRoom::class
)
var rounds: List<RoundWithTopic>? = null
}
class RoundWithTopic(
@Embedded
var round: RoundRoom,
@Embedded(prefix = "topic_")
var topic: Topic
)
Topic
上嵌入的注释指定了前缀,因为存在冲突的id
属性。
可以获取这些类的Room Query
是:
@Query("SELECT Topic.id as topic_id, Topic.name as topic_name, (...), RoundRoom.* FROM RoundRoom INNER JOIN Topic ON RoundRoom.topicId = Topic.id")
abstract fun findRoundsWithTopics(): List<RoundWithTopic>
但是,构建项目给了我房间错误:
There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: topic_id)
即使当我警告有关实际存在哪些字段的警告时,这也是Room告诉我的内容:
Columns returned by the query: topic_id, topic_name, topic_description, topic_language, topic_keywords, topic_sourceUrls, topic_furtherUrls, topic_questions, order, gameId, topicId, status, id. Fields in cz.melkamar.sklapecka.model.RoundWithTopic: order, gameId, topicId, status, id, topic_id, topic_name, topic_description, topic_language, topic_keywords, topic_sourceUrls, topic_furtherUrls, topic_questions, topic_image.
查询结果中有topic_id
列! 为什么会出现此错误?
为了完整起见,这是实体:
@Entity
data class Game(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@Embedded
val gameConfigurationEmbed: GameConfigurationEmbed
)
data class GameConfigurationEmbed(
var secondsPerTurn: Int,
var maxSecondsPerTurn: Int,
var bonusSecondsPerAnswer: Int
)
@Entity(
foreignKeys = [
ForeignKey(
entity = Game::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("gameId"),
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Topic::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("topicId"),
onDelete = ForeignKey.CASCADE
)
]
)
@TypeConverters(RoomConverters::class)
data class RoundRoom(
val order: Int,
var gameId: Long,
val topicId: String,
var status: RoundStatus = RoundStatus.CREATED,
@PrimaryKey(autoGenerate = true)
val id: Long = 0
) {
enum class RoundStatus {
CREATED, UPCOMING, IN_PROGRESS, FINISHED
}
}
@Entity
data class Topic(
@PrimaryKey val id: String,
val name: String,
val description: String,
val language: String,
val keywords: List<String>,
val sourceUrls: List<String>,
val furtherUrls: List<String>,
val questions: List<String>,
val image: ByteArray?
)
经过研究,特别是看一下此链接: 我如何代表与Android Room的多对多关系? 我们发现的唯一答案是
要么创建一个手工的sql查询来处理这种类型的情况,即您具有多对多关系
要么
或者具有一个附加的连接实体,该实体随其他对象的更新而更新。 使用这种方法,您可以获取ID,然后根据需要创建其他查询
看来嵌入式字段和类型转换器未正确地用于观察问题。 我不想在该问题的解决方案中进行详细介绍,因为它试图使用复杂的关系并且无法在我的机器中测试它的复制。
但是我想提供有关使用Embedded字段和TypeConverter的见解。
让我们以上面的问题为例: 游戏表具有字段id, secondsPerTurn, maxSecondsPerTurn, bonusSecondsPerAnswer
。
可以创建如下所示的实体。
@Entity
data class Game(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@Embedded
val gameConfigurationEmbed: GameConfigurationEmbed
)
data class GameConfigurationEmbed(
var secondsPerTurn: Int,
var maxSecondsPerTurn: Int,
var bonusSecondsPerAnswer: Int
)
在SQLite表中,数据实际上存储在四个不同的列中,但是Room根据数据类结构执行CRUD操作,从而为开发人员提供了更高的便利性。
类型转换器
如果我们要存储非原始数据类型或@Embedded
不会涵盖的相同类型的数据,类型转换器将非常有用。
例如,一场足球比赛可以在两个地方举行:主场和客场。 Home和Away可以具有相同的字段名称,例如placeName, latitude, longitude
。 在这种情况下,我们可以创建数据类和类型转换器,如下所示:
data class GamePlace(
val placeName:String,
val latitude:String,
val longitude:String
)
@Entity
data class Game(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@Embedded
val gameConfigurationEmbed: GameConfigurationEmbed
@TypeConverters
var home: GamePlace? = null,
@TypeConverters
var away: GamePlace? = null,
)
object Converters {
private val gson = Gson()
@TypeConverter
@JvmStatic
fun fromGamePlace(gamePlace: GamePlace?): String? {
return if (gamePlace == null) null else gson.toJson(gamePlace)
}
@TypeConverter
@JvmStatic
fun toGamePlace(jsonData: String?): GamePlace? {
return if (jsonData.isNullOrEmpty()) null
else gson.fromJson(jsonData, object : TypeToken<GamePlace?>() {}.type)
}
}
使用类型转换器时,应在数据库类中定义转换器类,如下所示:
@Database(
entities = [Game::class /* ,more classes here*/],
version = 1
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun gameDao(): GameDao
//.....
}
我希望这将有助于处理Room中的 Embedded和TypeConverter 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.