Комната Android с отложенным внешним ключом - PullRequest
1 голос
/ 06 мая 2019

У меня есть несколько таблиц, которые периодически обновляются с сервера (Benefit, Branch, Coupon) и еще две таблицы, которые являются только локальными (FavoriteBenefit, UsedCoupon). Диаграмма ER выглядит следующим образом: ER diagram

Всякий раз, когда Benefit удаляется на сервере, я также хочу удалить соответствующий объект из FavoriteBenefit. Для этого я могу использовать onDelete = ForeignKey.CASCADE, и когда родительский элемент Benefit больше не существует в базе данных, FavoriteBenefit также удаляется. Звучит хорошо.

Проблема возникает всякий раз, когда я использую @Insert(onConflict = OnConflictStrategy.REPLACE) для обновления преимуществ в базе данных. REPLACE фактически выполняет DELETE и INSERT, но DELETE внутренне запускает onDelete из FavoriteBenefit, и в результате все данные в этой таблице также удаляются.

(Аналогичная проблема возникает с таблицами Coupon и UsedCoupon.)


Я ищу способ временно отключить ограничения внешнего ключа до конца транзакции. То есть не проверять внешние ключи во время транзакции, а только в конце транзакции. Я по-прежнему хочу, чтобы Room автоматически удаляла объекты, у которых нет действительного родителя.


Похоже, что пометка внешнего ключа как отложенного путем установки deferred = true в определении @ForeignKey должна делать именно то, что я пытаюсь достичь.

boolean deferred ()

Ограничение внешнего ключа может быть отложено до завершения транзакции. Это полезно, если вы делаете массовые вставки в базу данных за одну транзакцию. По умолчанию ограничения внешнего ключа являются непосредственными, но вы можете изменить их, установив для этого поля значение true.

Но даже когда я установил флаг deferred, это, кажется, не имеет никакого эффекта, потому что FavoriteBenefit все еще удаляется каждый раз.

Я правильно понимаю флаг deferred?

1 Ответ

0 голосов
/ 17 июля 2019

Не знаю, относится ли это к вам, но у меня была похожая проблема. Я попытался поставить флаг deferred в обоих местах: сам класс отношений и в качестве прагмы. В обоих случаях элементы были удалены из-за стратегии OnConflictStrategy.REPLACE (которая, как вы упомянули, выполняет операцию DELETE). Обходной путь, который я нашел, заключается в использовании "UPSERT -подобного" запроса. UPSERT Поддержка операторов была добавлена ​​в SQLite в прошлом году, поэтому Room еще не поддерживает ее, но вы можете написать что-то вроде этого:

@Dao
abstract class BaseDao<T> {

    /**
     * Insert an item in the database.
     *
     * @param item the item to be inserted.
     * @return The SQLite row id
     */
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insert(item: T): Long

    /**
     * Insert an array of items in the database.
     *
     * @param items the items to be inserted.
     * @return The SQLite row ids
     */
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insert(items: List<T>): List<Long>

    /**
     * Update an item from the database.
     *
     * @param item the item to be updated
     */
    @Update
    abstract fun update(item: T)

    /**
     * Update an array of items from the database.
     *
     * @param item the item to be updated
     */
    @Update
    abstract fun update(item: List<T>)

    @Transaction
    fun upsert(item: T) {
        val id = insert(item)
        if (id == -1L) {
            update(item)
        }
    }

    @Transaction
    fun upsert(items: List<T>) {
        val insertResult = insert(items)
        val updateList = mutableListOf<T>()

        for (i in insertResult.indices) {
            if (insertResult[i] == -1L) {
                updateList.add(items[i])
            }
        }

        if (updateList.isNotEmpty()) {
            update(updateList)
        }
    }
}

Логика кода проста - если таблица уже содержит записи (это проверяется после вставки с помощью фильтрации rowid s), то мы должны обновить их.

Кредиты

...