Получение «Ошибка ограничения UNIQUE» при использовании @Upsert в Android Room

Получение net.sqlcipher.database.SQLiteConstraintException: error code 19: UNIQUE constraint failed with : appointment.activity_id: при попытке сделать @Upsert с Android Room.

Насколько я понимаю, @Upsert будет обновлять записи, если строка существует, или вставлять, если ее нет.

Вот определение таблицы:

data class AppointmentEntity(

   @PrimaryKey
   @field:ColumnInfo(name = "activity_id")
   val activityId: Long,

   @field:ColumnInfo(name = "selected_owner")
   val selectedOwner: String,

   val activityStartTimestamp: Instant,

   val activityEndTimestamp: Instant,

   val activityRevisionTimestamp: Instant?,

   val appointmentTypeCode: AppointmentTypeCode,

   val contactName: String?,

   val activityDescription: String?,

   val recurringId: Long?,

   val cancelled: Boolean,

   val personalAppointment: Boolean,

   val outlookIndicator: Boolean,

   val allDayEvent: Boolean,

   val deleteDate: Instant?
 )

Вот код в @Dao:

@Upsert
fun insertAppointments(appointment: List<AppointmentEntity>)

Почему я получаю уникальную ошибку ограничения. Разве существующая запись не будет просто обновлена, а не повторно вставлена?

Из документации по андроиду:

Если целевая сущность содержит автоматически сгенерированный PrimaryKey, то классу POJO не требуется эквивалентное поле первичного ключа, в противном случае первичные ключи также должны присутствовать в POJO. Если первичный ключ уже существует, будут обновлены только столбцы, представленные полями частичной сущности.

0
0
79
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Насколько я понимаю, @Upsert будет обновлять записи, если строка существует, или вставлять, если ее нет.

Это должно делать и использовать ваш доступный код, но модифицированный, чтобы не использовать преобразователи типов, он делает. например с использованием:-

@Entity(tableName = "appointment")
data class AppointmentEntity(

    @PrimaryKey
    @field:ColumnInfo(name = "activity_id")
    val activityId: Long,
    @field:ColumnInfo(name = "selected_owner")
    val selectedOwner: String,
    val activityStartTimestamp: Long?=System.currentTimeMillis() / 1000, /*<<<< CHANGED FROM Instant */
    val activityEndTimestamp: Long?=System.currentTimeMillis() / 1000,  /*<<<< CHANGED FROM Instant */
    val activityRevisionTimestamp: Long?=System.currentTimeMillis() / 1000,  /*<<<< CHANGED FROM Instant */
    //val appointmentTypeCode: AppointmentTypeCode, /*<<<<< COMMENTED OUT */
    val contactName: String?,
    val activityDescription: String?,
    val recurringId: Long?,
    val cancelled: Boolean,
    val personalAppointment: Boolean,
    val outlookIndicator: Boolean,
    val allDayEvent: Boolean,
    val deleteDate: Long?=null  /*<<<< CHANGED FROM Instant */
)

@Database(entities = [AppointmentEntity::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDao(): AllDao

    companion object {
        private var instance: TheDatabase?=null
        fun getInstance(context: Context): TheDatabase {
            if (instance==null) {
                instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}

@Dao
interface AllDao {
    @Upsert
    fun insertAppointments(appointment: List<AppointmentEntity>)
}

и :-

class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AllDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var appointmentEntity1 = AppointmentEntity(activityId = 1, selectedOwner = "O001", contactName = "C001", recurringId = 0, cancelled = false, activityDescription = "Desc001", personalAppointment = true, outlookIndicator = false, allDayEvent = false)
        var appointmentEntity2 = AppointmentEntity(activityId = 1, selectedOwner = "O001", contactName = "C002", recurringId = 0, cancelled = false, activityDescription = "Desc002", personalAppointment = true, outlookIndicator = false, allDayEvent = false)

        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()

        dao.insertAppointments(listOf(appointmentEntity1,appointmentEntity2,appointmentEntity1,appointmentEntity2))
    }
}

Результаты успешного запуска с отображением проверки приложений: -

  • т. е. он справился с 4 UPSERTS, и результирующие данные соответствуют ожидаемым.

Проблема скорее всего в другом. Возможно, потому что вы используете sqlcipher или, возможно, другие действия с базой данных.

К сожалению, мне нужно использовать SQLcipher и преобразователи типов. Кроме того, на самом деле это не отвечает на вопрос, почему он пытается повторно вставить запись, а не просто обновить ее. Другое дело, вы запускали вставки снова и снова, чтобы увидеть, были ли они переустановлены или обновлены? Но я очень ценю усилия, которые вы приложили, чтобы помочь.

Kristy Welsh 01.12.2022 03:41

@KristyWelsh не говорит, что это проблемы (преобразователи типов не использовались исключительно для простоты / удобства теста), просто, возможно, sqlcipher может быть (не должен быть).

MikeT 01.12.2022 04:12
Ответ принят как подходящий

Возник конфликт между Android room 2.5.0 и sqlcipher. Как только я удалил sqlcipher SupportFactory из базы данных моей комнаты, upsert заработал, как и ожидалось.

Похоже, это основная причина: https://github.com/sqlcipher/android-database-sqlcipher/issues/588 . SqlCipher не расширяет android.database.SQLException, поэтому, когда Room проверяет, является ли исключение внешним/уникальным ключом здесь: https://android-review.googlesource.com/c/platform/frameworks/support/+/2191947 /9/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt#209 он просто всегда выдает исключение уникального ключа net.sqlcipher.database.SQLException, а не обновляет существующую запись.

Спасибо. Я размышлял!

Kristy Welsh 12.01.2023 16:50

Другие вопросы по теме