Я где-то читал (не помню источник), что Upsert обычно возвращает -1 в случае возникновения ошибки. Недавно в своем проекте я заметил, что Upsert возвращает -1, хотя обновление прошло успешно.
Соответствующие части кода:
***Комната устроена как:
@Entity(tableName = "birth_defect_beneficiary_details")
data class BirthDefectBabyDetails(
@PrimaryKey(autoGenerate = true)
val id: Int? = null,
val dateOfBirth: Long,
val sex: String,
val motherName: String,
val fatherName: String,
val address: String,
val contactNumber: String,
val condition: String,
val patientContacted: Boolean
)
@Dao
interface BDBabyDetailsDao {
@Query("SELECT * FROM birth_defect_beneficiary_details ORDER BY dateOfBirth DESC")
fun getDetailsOfAllBabies(): Flow<List<BirthDefectBabyDetails>>
@Upsert
suspend fun upsertEntry(entry: BirthDefectBabyDetails): Long
@Delete
suspend fun deleteEntry(entry: BirthDefectBabyDetails): Int
@Query("DELETE FROM birth_defect_beneficiary_details")
suspend fun deleteAllEntries()
}
@Database(
entities = [BirthDefectBabyDetails::class],
version = 1,
exportSchema = false
)
abstract class BDBabiesDatabase : RoomDatabase() {
abstract val babyDao: BDBabyDetailsDao
}
interface BabyDetailsEntryRepository {
fun getAllBabiesStream(): Flow<List<BirthDefectBabyDetails>>
suspend fun upsertEntry(entry: BirthDefectBabyDetails): Long
suspend fun deleteEntry(entry: BirthDefectBabyDetails): Int
suspend fun deleteAllEntries()
}
class BDBabyDetailsLocalRepository @Inject constructor(
private val babyDao: BDBabyDetailsDao
) : BabyDetailsEntryRepository {
override fun getAllBabiesStream(): Flow<List<BirthDefectBabyDetails>> {
return babyDao.getDetailsOfAllBabies()
}
override suspend fun upsertEntry(entry: BirthDefectBabyDetails): Long {
return babyDao.upsertEntry(entry = entry)
}
override suspend fun deleteEntry(entry: BirthDefectBabyDetails): Int {
return babyDao.deleteEntry(entry = entry)
}
override suspend fun deleteAllEntries() {
babyDao.deleteAllEntries()
}
}
Внутри ViewModel, где происходит фактическое «обновление»:
private fun upsertEntry() {
// some codes...
viewModelScope.launch(IO) {
try {
val result = babyRepository.upsertEntry(newEntry)
Log.d("DPDC", "$result")
if (result != -1L) {
triggerEvent(VMEvents.EntryAdded("B/O ${newEntry.motherName}"))
} else {
triggerEvent(VMEvents.ShowError("Failed to add/update entry"))
}
} catch (e: Exception) {
triggerEvent(VMEvents.ShowError("Error occurred: ${e.message}"))
}
}
// some more codes...
}
newEntry is declared as val newEntry: BirthDefectBabyDetails
Теперь с помощью проверки приложений в Android Studio это база данных перед редактированием:
Записать вывод cat после редактирования:
db после редактирования (обратите внимание — последняя запись — я изменил пол с женского на мужской:
В этот момент я в замешательстве - что происходит. Несмотря на то, что мое приложение ведет себя так, как должно, Logcat рассказывает другую историю. Что мне не хватает?
Для большей ясности это зависимости в build.gradle.kts (Модуль: приложение).
// room
implementation("androidx.room:room-ktx:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")
Я где-то читал (не помню источник), что Upsert обычно возвращает -1 в случае возникновения ошибки.
Возвращаемое значение — это значение столбца id (в вашем случае), ЕСЛИ строка была вставлена, а не обновлена. Если ни одна строка не была вставлена и, следовательно, строка (в соответствии с сопоставляемым первичным ключом) была обновлена (даже если значения могли быть изменены, а могли и не быть фактически изменены), то возвращается -1.
Это возвращаемое значение согласуется с INSERT (который возвращает идентификатор вставленной строки), но несовместимо с UPDATE (который возвращает int/Int числа затронутых строк).
Было бы еще более запутанно и, возможно, катастрофично, если бы возвращалось количество затронутых (обновленных) строк, поскольку тогда вы не смогли бы отличить, какие именно, и во многих ситуациях будет возвращена 1, которая может рассматриваться как идентификатор строки.
Если вы углубитесь в основной код комнаты, то есть EntityUpsertionAdapter, то: -
Ошибка?????
Как можно видеть, из-за try/catch ошибки будут считаться результатом конфликта ограничений уникальности (первичный ключ неявно уникален), даже если это не так. Однако благодаря предварительным проверкам Room несколько ошибок не возникнут (например, столбец не найден, что было бы синтаксической ошибкой SQLite, не должно возникать при использовании кода, сгенерированного комнатой @....).
Однако вот что выходит за рамки того, что Room может проверить. Короче говоря, вставляется строка, которая представляет собой максимально возможное значение столбца идентификатора. Из-за подразумеваемого использования ограничения AUTOINCREMENT (фактически то, что реализует autoGenerate=true), при попытке добавить еще одну строку возникает ошибка SQLTE_FULL.
Итак, используя: -
const val max_rowid = 9223372036854775807
....
val b5 = BirthDefectBabyDetails(max_rowid,b1.dateOfBirth,b1.sex,"M001","F001",b1.address,b1.contactNumber,b1.condition,b1.patientContacted)
upsertLoggingFullResult(b5)
upsertLoggingFullResult(BirthDefectBabyDetails(null,200,b4.sex,b4.motherName,b4.fatherName,b4.address,b4.contactNumber,b4.condition,b4.patientContacted))
:-
fun upsertLoggingFullResult(b: BirthDefectBabyDetails): Long {
val result: Long = dao.upsertEntry(b)
Log.d("DBINFO_UPSRT","Result of upsert ${result}, when inserting ID = {${b.id} DOB=${b.dateOfBirth} Mother=${b.motherName} Father=${b.fatherName} ...." )
logAll("aftupsrt")
return result
}
fun logAll(tag_suffix: String) {
for (b in dao.getDetailsOfAllBabies()) {
Log.d("DBINFO_$tag_suffix","ID=${b.id} DOB=${b.dateOfBirth} Mother=${b.motherName} Father=${b.fatherName} ....")
}
}
Тогда журнал включает в себя: -
2024-06-11 11:08:41.460 D/DBINFO_aftupsrt: ID=9223372036854775807 DOB=100 Mother=M001 Father=F001 ....
2024-06-11 11:08:41.461 E/SQLiteLog: (13) statement aborts at 4: [INSERT INTO `birth_defect_beneficiary_details` (`id`,`dateOfBirth`,`sex`,`motherName`,`fatherName`,`address`,`contactNumber`,`condition`,`patientContacted`) VALUES (?,?,?,?,?,?,?,?,?)]
2024-06-11 11:08:41.461 E/SQLiteLog: (1) statement aborts at 1: [ROLLBACK;] cannot rollback - no transaction is active
2024-06-11 11:08:41.462 D/AndroidRuntime: Shutting down VM
Дополнительный комментарий: -
Итак, в текущей версии Room можно с уверенностью предположить, что upsert вернет -1 в случае успешного обновления. Как в таком случае проверить, не удалось ли выполнить обновление. Или мне следует вообще отказаться от Upsert в пользу Insert и Update?
Крайне маловероятно, что обновление завершится неудачей (по крайней мере, с вашим кодом), поэтому -1 обычно указывает на успешное обновление.
insertOrUpdate не удастся, за исключением случая, когда UPDATE завершится неудачей (обратите внимание, что отсутствие изменения значений, поскольку они не были изменены (например, № 2 в примере ниже), не считается сбоем)Тем не менее, вы могли бы рассмотреть что-то вроде: -
Класс, такой как: -
data class InsertOrUpdateReturnValues(
val rowid: Long=-1,
val affectedRows: Int=0
)
и следующие функции в аннотированном интерфейсе /an @Dao:
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertOrIgnore(birthDefectBabyDetails: BirthDefectBabyDetails): Long
@Update(onConflict = OnConflictStrategy.IGNORE)
fun update(birthDefectBabyDetails: BirthDefectBabyDetails): Int
@Transaction
@Query("")
fun insertOrUpdate(birthDefectBabyDetails: BirthDefectBabyDetails): InsertOrUpdateReturnValues {
var updateResult=0
val insertResult = insertOrIgnore(birthDefectBabyDetails)
if (insertResult.equals(-1L)){
updateResult = update(birthDefectBabyDetails)
}
return InsertOrUpdateReturnValues(insertResult,updateResult)
}
Пример использования в действии: -
dao.deleteAllEntries()
logAll("AFTDLTALL")
var insertOrUpdateReturnValues: InsertOrUpdateReturnValues = dao.insertOrUpdate(b1)
val b1id = insertOrUpdateReturnValues.rowid
Log.d("DBINFO_AFTIOU1","Insert result (rowid) = ${insertOrUpdateReturnValues.rowid} Update result = ${insertOrUpdateReturnValues.affectedRows}")
logAll("AFTIOU1")
insertOrUpdateReturnValues = dao.insertOrUpdate(BirthDefectBabyDetails(b1id,b1.dateOfBirth,b1.sex,b1.motherName,b1.fatherName,b1.address,b1.contactNumber,b1.condition,b1.patientContacted))
Log.d("DBINFO_AFTIOU2","Insert result (rowid) = ${insertOrUpdateReturnValues.rowid} Update result = ${insertOrUpdateReturnValues.affectedRows}")
logAll("AFTIOU2")
insertOrUpdateReturnValues = dao.insertOrUpdate(BirthDefectBabyDetails(b1id,b1.dateOfBirth+500,b1.sex,b1.motherName,b1.fatherName,b1.address,b1.contactNumber,b1.condition,b1.patientContacted))
Log.d("DBINFO_AFTIOU3","Insert result (rowid) = ${insertOrUpdateReturnValues.rowid} Update result = ${insertOrUpdateReturnValues.affectedRows}")
logAll("AFTIOU3")
Результаты: -
2024-06-11 20:25:00.146 D/DBINFO_AFTIOU1: Insert result (rowid) = 1 Update result = 0
2024-06-11 20:25:00.147 D/DBINFO_AFTIOU1: ID=1 DOB=100 Mother=Mother001 Father=Father001 ....
2024-06-11 20:25:00.151 D/DBINFO_AFTIOU2: Insert result (rowid) = -1 Update result = 1
2024-06-11 20:25:00.152 D/DBINFO_AFTIOU2: ID=1 DOB=100 Mother=Mother001 Father=Father001 ....
2024-06-11 20:25:00.162 D/DBINFO_AFTIOU3: Insert result (rowid) = -1 Update result = 1
2024-06-11 20:25:00.164 D/DBINFO_AFTIOU3: ID=1 DOB=600 Mother=Mother001 Father=Father001 ....
Поскольку OnConflictStrategy имеет значение IGNORE, только зафиксированные конфликты ограничений не приведут к исключению. У вас есть немного больше контроля, поскольку вы можете использовать другую стратегию для одного или обоих INSERT и UPDATE.
Какой путь, вероятно, не имеет особого значения, хотя обычно будет применяться одно и то же поведение и конечный результат будет таким же.
@TheSelfTaughtNoob обновил ответ.
эта строка не имеет смысла: @Update(onConflict = OnConflictStrategy.IGNORE). Это, вероятно, проигнорирует все изменения. Почему не только аннотация @Update? Кроме того, здесь (Developer.android.com/reference/android/arch/persistence/room/…) говорится, что onConflictStrategy устарела.
Что означает onConflictStrategy,IGNORE в контексте @Update?
@TheSelfTaughtNoob Не все изменения будут игнорироваться (как показано в примере, фактически меняющем dob). Устаревание связано с кодом Android, а не с androidx, см. Developer.android.com/reference/androidx/room/… (FAIL и ROLLBACK устарели). О том, как SQLite обрабатывает ON CONFLICT, см. sqlite.org/lang_conflict.html
Запоздалое спасибо за ответ.
Итак, в текущей версии Room можно с уверенностью предположить, что upsert вернет -1 в случае успешного обновления. Как в таком случае проверить, не удалось ли выполнить обновление. Или мне следует вообще отказаться от Upsert в пользу Insert и Update?