当我想在 Room 中存储数据时,NOT NULL 约束失败

NOT NULL constraint failed when I want to store data in Room

我尝试为我的应用建立离线支持。这是我 运行 应用程序时的异常错误:

2019-04-19 13:44:44.768 8549-8549/com.sample.android.superhero E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.sample.android.superhero, PID: 8549
    android.database.sqlite.SQLiteConstraintException: NOT NULL constraint failed: heroes.power_id (code 1299)
    #################################################################
    Error Code : 1299 (SQLITE_CONSTRAINT_NOTNULL)
    Caused By : Abort due to constraint violation.
        (NOT NULL constraint failed: heroes.power_id (code 1299))
    #################################################################
        at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
        at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:953)
        at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
        at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
        at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.java:51)
        at androidx.room.EntityInsertionAdapter.insert(EntityInsertionAdapter.java:64)
        at com.sample.android.superhero.data.source.local.HeroDao_Impl.insertHero(HeroDao_Impl.java:189)
        at com.sample.android.superhero.data.source.local.SuperHeroLocalDataSource$saveHero.invokeSuspend(SuperHeroLocalDataSource.kt:27)
        at com.sample.android.superhero.data.source.local.SuperHeroLocalDataSource$saveHero.invoke(Unknown Source:10)
        at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:146)
        at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
        at com.sample.android.superhero.data.source.local.SuperHeroLocalDataSource.saveHero(SuperHeroLocalDataSource.kt:26)
        at com.sample.android.superhero.data.source.SuperHeroRepository.refreshLocalDataSource(SuperHeroRepository.kt:55)
        at com.sample.android.superhero.data.source.SuperHeroRepository.getHeroesFromRemoteDataSource(SuperHeroRepository.kt:45)
        at com.sample.android.superhero.data.source.SuperHeroRepository$getHeroesFromRemoteDataSource.invokeSuspend(Unknown Source:12)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.ResumeModeKt.resumeUninterceptedMode(ResumeMode.kt:46)
        at kotlinx.coroutines.internal.ScopeCoroutine.onCompletionInternal$kotlinx_coroutines_core(Scopes.kt:28)
        at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:305)
        at kotlinx.coroutines.JobSupport.tryFinalizeSimpleState(JobSupport.kt:264)
        at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:762)
        at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:742)
        at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:117)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)

我的英雄模型如下:

@Entity(tableName = "heroes")
class Hero(
    @PrimaryKey @ColumnInfo(name = "entryid")
    val id: String,
    val name: String,
    @Embedded(prefix = "power_")
    val power: Powerstates,
    @Embedded(prefix = "bio_")
    val bio: Biography,
    @Embedded(prefix = "appearance_")
    val appearance: Appearance,
    @Embedded(prefix = "work_")
    val work: Work,
    @Embedded
    val image: Image
)

这是我的 Power 模型:

@Entity(
    tableName = "powerstates",
    foreignKeys = [
        ForeignKey(
            entity = Hero::class,
            parentColumns = ["entryid"],
            childColumns = ["heroId"],
            onDelete = ForeignKey.CASCADE
        )], indices = [Index("heroId")]
)
class Powerstates(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    val heroId: Int,
    val intelligence: Int,
    val strength: Int,
    val speed: Int,
    val power: Int,
    val combat: Int
)

这是保存和检索数据的逻辑:

override suspend fun getHeroes(query: String): Result<List<Hero>> {
        return if (cacheIsDirty) {
            // If the cache is dirty we need to fetch new data from the network.
            getHeroesFromRemoteDataSource(query)
        } else {
            // Query the local storage if available. If not, query the network.
            when (val result = localDataSource.getHeroes(query)) {
                is Result.Success -> Result.Success(result.data)
                is Result.Error -> getHeroesFromRemoteDataSource(query)
            }
        }
    }

    private suspend fun getHeroesFromRemoteDataSource(query: String): Result<List<Hero>> {
        return when (val result = remoteDataSource.getHeroes(query)) {
            is Result.Success -> {
                refreshLocalDataSource(result.data)
                Result.Success(result.data)
            }
            is Result.Error -> Result.Error(result.exception)
        }
    }

    private suspend fun refreshLocalDataSource(heroes: List<Hero>) {
        localDataSource.deleteAllHeroes()
        for (hero in heroes) {
            localDataSource.saveHero(hero)
        }
    }

我尝试了以下解决方案: 但是我得到了同样的错误。

这是我的 Dao:

@Dao
interface HeroDao {

    @Query("SELECT * FROM Heroes WHERE name LIKE :query")
    fun getHeroes(query: String): List<Hero>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertHero(hero: Hero)
}

您正在使用 Powerstates 作为 @Embedded 对象,而不是单独的实体。因此,从 Powerstates 中删除 @PrimaryKey@Entity,以及(显然)不必要的列。你应该结束:

class Powerstates(
    val intelligence: Int,
    val strength: Int,
    val speed: Int,
    val power: Int,
    val combat: Int
)

正如 CommonsWare 提到的:您可能想查看生成的 Room 代码(类 在您的堆栈跟踪中列出)并查看可能出了什么问题。 – CommonsWare

最好调试生成的代码,您将获得所需的信息。就我而言,它有所帮助。