You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Room数据库Projekt表删除异常:删除最后一条笔记后项目未移除

问题描述

从Room数据库的projekte表中删除指定项目。数据库包含projekte、locations、notizen三张表,其中notizen表与另外两张表存在外键关联。目前已实现删除笔记及项目的方法,笔记可正常删除,但删除最后一条笔记后,对应的项目并未从数据库中移除。

实体类与数据库定义代码

@Entity(tableName = "projekte")
data class Projekt(@PrimaryKey val id: Long, var beschreibung: String?) {
    fun getDescription(): String {
        val dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT) // Import!!
        val dateString = dateFormat.format(id)
        return if (beschreibung.isNullOrEmpty()) dateString else "$beschreibung ($dateString)"
    }
}

@Entity (tableName = "locations", primaryKeys = ["latitude", "longitude"])
data class Location(val latitude: Double, val longitude: Double,
val altitude: Double, val provider: String)

@VersionedParcelize
@Entity (tableName = "notizen",
    foreignKeys = [ForeignKey(entity = Projekt::class,
    parentColumns = ["id"], childColumns = ["projektId"]),
        ForeignKey(entity = Location::class, parentColumns = ["latitude", "longitude"],
            childColumns = ["latitude", "longitude"])])
data class Notiz(@PrimaryKey(autoGenerate = true) var id: Long? = null,
val projektId: Long,
val latitude: Double,
val longitude: Double,
var thema: String,
var notiz: String):Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readValue(Long::class.java.classLoader) as? Long,
        parcel.readLong(),
        parcel.readDouble(),
        parcel.readDouble(),
        parcel.readString().toString(),
        parcel.readString().toString()
    ) {
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeValue(id)
        parcel.writeLong(projektId)
        parcel.writeDouble(latitude)
        parcel.writeDouble(longitude)
        parcel.writeString(thema)
        parcel.writeString(notiz)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Notiz> {
        override fun createFromParcel(parcel: Parcel): Notiz {
            return Notiz(parcel)
        }

        override fun newArray(size: Int): Array<Notiz?> {
            return arrayOfNulls(size)
        }
    }
}

@Database(entities = [Projekt::class, Notiz::class, Location::class], version = 1)
abstract class GeoNotesDatabase : RoomDatabase() {
    companion object {
        private var INSTANCE: GeoNotesDatabase? = null
        fun getInstance(context: Context): GeoNotesDatabase {
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(context, GeoNotesDatabase::class.java, "GeoNotesDatabase").build()
            }
            return INSTANCE as GeoNotesDatabase
        }
    }
    abstract fun projekteDao(): ProjekteDao
    abstract fun locationDao(): LocationDao
    abstract fun notizenDao(): NotizenDao
}

@Dao 
interface ProjekteDao {
    @Insert (onConflict = OnConflictStrategy.REPLACE)
    fun insertProjekt (projekt: Projekt): Long

    @Query("SELECT * FROM projekte")
    fun getProjekte() : List<Projekt>

    @Query ("SELECT * FROM projekte where id=:id")
    fun getProjekt(id: Long): Projekt

    @Query ("DELETE FROM projekte where id= :id")
    fun deleteProjekt(id: Long?)

    @Update
    fun updateProjekt(projekt: Projekt)
}

@Dao 
interface LocationDao {
    @Insert (onConflict = OnConflictStrategy.IGNORE)
    fun insertLocation(location: Location): Long

    @Query ("SELECT * FROM locations")
    fun getLocations(): List<Location>
}

@Dao 
interface NotizenDao {
    @Insert (onConflict = OnConflictStrategy.REPLACE)
    fun insertNotiz(notiz: Notiz): Long

    @Query ("SELECT * FROM notizen where projektId = :projektId")
    fun getNotizen(projektId: Long): List<Notiz>

    @Query ("SELECT * FROM notizen where projektId = :projektId AND id < :notizId ORDER BY id ASC")
    fun getPreviousNotizen(notizId: Long?, projektId: Long): List<Notiz>

    @Query ("SELECT * FROM notizen where projektId = :projektId AND id > :notizId ORDER BY id ASC")
    fun getNextNotizen(notizId: Long?, projektId: Long): List<Notiz>

    @Query ("SELECT * FROM notizen where id = :id")
    fun getNotiz(id: Long?): Notiz

    @Query("DELETE FROM notizen where id = :id")
    fun deleteNotiz(id: Long?)
}

删除方法代码

fun notizLoeschen() {
    //Notiz Löschen
    val textViewThema = findViewById<TextView>(R.id.edittext_thema)
    val textViewNotiz = findViewById<TextView>(R.id.edittext_notiz)
    var notizen: List<Notiz>? = null
    if (aktuelleNotiz == null) return
    val database = GeoNotesDatabase.getInstance(this)
    CoroutineScope(Dispatchers.Main).launch {
        withContext(Dispatchers.IO) {
            notizen = database.notizenDao().getNotizen(aktuellesProjekt.id)
        }

        if (notizen?.size!! > 1) {
            withContext(Dispatchers.IO) {
                database.notizenDao().deleteNotiz(aktuelleNotiz!!.id)
            }
            aktuelleNotiz = null
            textViewThema.text = ""
            textViewNotiz.text = ""
            return@launch

        }else {
            with(AlertDialog.Builder(this@GatherActivity)) {
                setTitle("Löschen der letzten Notiz löscht das Projekt. Forfahren?")
                setPositiveButton("OK", DialogInterface.OnClickListener { dialog, id ->
                    CoroutineScope(Dispatchers.Main).launch {

                        withContext(Dispatchers.IO) {
                            aktuelleNotiz = notizen!!.last()
                            Log.d(javaClass.simpleName, "TEST P: ${aktuelleNotiz?.id} P: ${aktuellesProjekt.id}") // Testweise eine Ausgabe der beiden ID's
                            database.notizenDao().deleteNotiz(aktuelleNotiz!!.id)
                            database.projekteDao().deleteProjekt(aktuellesProjekt!!.id)
                            aktuellesProjekt = Projekt(Date().time, "")
                            database.projekteDao().insertProjekt(aktuellesProjekt)

                        }
                    }
                    aktuellesProjekt = Projekt(Date().time, "")
                    findViewById<TextView>(R.id.textview_aktuelles_projekt).text = getString(R.string.aktuelles_projekt_prefix) + aktuellesProjekt.getDescription()
                    aktuelleNotiz = null
                    textViewThema.text = ""
                    textViewNotiz.text = ""

                })
                setNegativeButton(
                    "ABBRECHEN",
                    DialogInterface.OnClickListener { dialog, id -> })
                show()
            }
        }
    }
}

问题分析与修复方案

核心问题

代码逻辑存在矛盾:删除最后一条笔记和对应项目后,立刻创建了一个新的Projekt并插入到数据库,导致数据库里始终存在项目,看起来像是原项目未被删除。

修复步骤

  1. 移除多余的项目插入代码
    在IO协程中,删除完笔记和项目后,删掉以下两行代码:

    aktuellesProjekt = Projekt(Date().time, "")
    database.projekteDao().insertProjekt(aktuellesProjekt)
    

    修改后协程内代码:

    withContext(Dispatchers.IO) {
        aktuelleNotiz = notizen!!.last()
        Log.d(javaClass.simpleName, "TEST P: ${aktuelleNotiz?.id} P: ${aktuellesProjekt.id}")
        database.notizenDao().deleteNotiz(aktuelleNotiz!!.id)
        database.projekteDao().deleteProjekt(aktuellesProjekt!!.id)
    }
    

    这样旧项目会被彻底删除,不会有新项目自动插入。

  2. 优化外键约束(可选但推荐)
    当前Notiz表的外键未设置级联删除,需要手动删除笔记后再删项目。可以给外键添加onDelete = ForeignKey.CASCADE,删除Projekt时关联的Notiz会自动删除,简化代码:
    修改Notiz实体的外键定义:

    foreignKeys = [ForeignKey(entity = Projekt::class,
        parentColumns = ["id"], 
        childColumns = ["projektId"],
        onDelete = ForeignKey.CASCADE), // 添加级联删除
        ForeignKey(entity = Location::class, 
            parentColumns = ["latitude", "longitude"],
            childColumns = ["latitude", "longitude"])]
    
  3. 修正UI更新逻辑
    原代码在协程外重复创建aktuellesProjekt,会导致UI显示与数据库状态不一致。建议将UI更新代码移到协程的withContext(Dispatchers.Main)中,确保状态同步:

    CoroutineScope(Dispatchers.Main).launch {
        withContext(Dispatchers.IO) {
            // 数据库操作
        }
        // 同步更新UI
        aktuellesProjekt = null
        findViewById<TextView>(R.id.textview_aktuelles_projekt).text = getString(R.string.aktuelles_projekt_prefix) + "无项目"
        aktuelleNotiz = null
        textViewThema.text = ""
        textViewNotiz.text = ""
    }
    

内容的提问来源于stack exchange,提问作者Andreas Kirchberger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 13:09:58