Android Room使用延迟的外键

9
我有几个表从服务器定期更新(Benefit, Branch, Coupon),还有两个本地表(FavoriteBenefit, UsedCoupon)。ER图如下: ER diagram 每当服务器上的Benefit被删除时,我也想从FavoriteBenefit中删除相应的实体。为此,我可以使用onDelete = ForeignKey.CASCADE,只要父级Benefit不再存在于数据库中,FavoriteBenefit也会被删除。听起来很好。
但是当我使用@Insert(onConflict = OnConflictStrategy.REPLACE)来更新数据库中的福利时,问题就出现了。实际上,REPLACE执行了DELETEINSERT,但DELETE在内部触发了FavoriteBenefitonDelete,导致该表中的所有数据也被删除了。
(类似的问题也出现在 CouponUsedCoupon 表中。)
我正在寻找一种方法,可以在事务结束之前暂时禁用外键约束。也就是说,在事务期间不验证外键,但只在事务结束时进行验证。 我仍希望Room自动删除没有有效父项的实体。

看起来通过在@ForeignKey定义上设置deferred = true来标记外键为延迟应该正是我想要实现的。

boolean deferred ()

外键约束可以推迟到事务完成时再执行。如果您正在单个事务中对数据库进行批量插入,则此功能非常有用。默认情况下,外键约束是立即执行的,但您可以通过将此字段设置为true来更改它。

但是,即使我设置了deferred标志,似乎也没有任何效果,因为每次仍然会删除FavoriteBenefit

我是否错误地理解了deferred标志?

3个回答

6

我不知道这个问题是否仍然与您相关,但我有类似的问题。 我尝试在关系类本身和作为pragma中都加入了 deferred 标志。 在两种情况下,项目都因 OnConflictStrategy.REPLACE 策略而被删除(正如您所提到的执行 DELETE 操作)。 我发现的解决方法是使用类似 "UPSERT" 的查询。 SQLite去年添加了对 UPSERT 语句的支持,因此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进行检查),我们应该更新它们。

来源


4
原因是ON DELETE CASCADE是一种类似于触发器的操作,它会立即执行。请参见该问题评论
此外,PostgreSQL团队还有一个更详细的解释
引用检查可以延迟到事务结束,但“参考操作”不能延迟。它们总是在触发语句期间发生。例如,SQL99将级联删除的结果描述为引用行立即“标记为删除”,然后
15. 标记为删除的所有行在SQL语句结束之前有效删除,而不需要检查任何完整性约束条件。
另请查看类似的SQLite问题,其中包含一些解决方法。

1

我之前遇到过这个问题,通过创建一个称为delsert的新语法来解决它。
以下是一个示例:

    @Query("DELETE FROM patient")
    public abstract void delete();

    @Query("DELETE FROM patient WHERE p_id IN (SELECT p_id FROM patient WHERE p_id NOT IN (:ids))")
    public abstract void deleteUnused(List<Long> ids);

    @Transaction
    public void delsert(List<Patient> patientList) {
        if (CommonUtils.isListEmpty(patientList)) {
            this.delete();
            return;
        }

        List<Long> idsPatient = new ArrayList<>();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
            patientList.forEach(order -> idsPatient.add(order.getId()));
        else
            for (Patient id : patientList) idsPatient.add(id.getId());

        deleteUnused(idsPatient);

        insert(patientList);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接