Получение 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. Если первичный ключ уже существует, будут обновлены только столбцы, представленные полями частичной сущности.
Насколько я понимаю, @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))
}
}
Результаты успешного запуска с отображением проверки приложений: -
Проблема скорее всего в другом. Возможно, потому что вы используете sqlcipher или, возможно, другие действия с базой данных.
@KristyWelsh не говорит, что это проблемы (преобразователи типов не использовались исключительно для простоты / удобства теста), просто, возможно, sqlcipher может быть (не должен быть).
Возник конфликт между 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, а не обновляет существующую запись.
Спасибо. Я размышлял!
К сожалению, мне нужно использовать SQLcipher и преобразователи типов. Кроме того, на самом деле это не отвечает на вопрос, почему он пытается повторно вставить запись, а не просто обновить ее. Другое дело, вы запускали вставки снова и снова, чтобы увидеть, были ли они переустановлены или обновлены? Но я очень ценю усилия, которые вы приложили, чтобы помочь.