繁体   English   中英

Android Room和POJO上的多个@Embedded字段出现“无此列” SQL错误

[英]“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%肯定是@RelationOwnerWithEverything 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的多对多关系? 我们发现的唯一答案是

  1. 要么创建一个手工的sql查询来处理这种类型的情况,即您具有多对多关系

    要么

  2. 或者具有一个附加的连接实体,该实体随其他对象的更新而更新。 使用这种方法,您可以获取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中的 EmbeddedTypeConverter

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM