带有延迟外键的Android Room

时间:2019-05-06 10:53:24

标签: android foreign-keys android-room deferred

我有几个从服务器定期更新的表(BenefitBranchCoupon)和另外两个仅是本地的表(FavoriteBenefit,{{ 1}})。 ER图如下所示: ER diagram

无论何时在服务器上删除UsedCoupon时,我都还想从Benefit中删除适当的实体。为此,我可以使用FavoriteBenefit,并且在数据库中不再存在父项onDelete = ForeignKey.CASCADE时,Benefit也将被删除。听起来不错。

我每次使用都会出现问题 FavoriteBenefit以更新数据库中的收益。 @Insert(onConflict = OnConflictStrategy.REPLACE)实际上执行REPLACEDELETE,但是INSERT内部触发DELETE中的onDelete,因此该表中的所有数据都被删除也是

FavoriteBenefitCoupon表也会发生类似的问题。)


我正在寻找一种方法来临时禁用外键约束,直到交易结束。也就是说,请勿在交易过程中验证外键,而只能在交易结束时验证外键。 我仍然希望Room自动删除没有有效父代的实体。


似乎通过在UsedCoupon定义上设置deferred = true来标记foreign key as defferred应该正是我想要达到的目标。

  

布尔延迟()

     

可以将外键约束推迟到事务完成之前。如果您要在单个事务中向数据库中批量插入,这将很有用。默认情况下,外键约束是即时的,但是您可以通过将此字段设置为true来更改它。

但是即使我设置了@ForeignKey标志,它似乎也没有任何作用,因为deferred每次仍在被删除。

我误解了FavoriteBenefit标志吗?

3 个答案:

答案 0 :(得分:3)

原因是ON DELETE CASCADE是类似于触发器的动作,它会立即执行。请参见issuecomment

PostgreSQL团队还提供了更详细的explanation

是的,据我们所知,这是根据SQL规范。约束检查可以 推迟到交易结束,但“推荐行为”不是 延期的它们总是在触发语句期间发生。对于 实例SQL99将级联删除的结果描述为 引用行立即被“标记为删除”,然后

  1. 所有标记为删除的行均被有效删除 在SQL语句的末尾,在检查任何 完整性约束。

看看similar SQLite question,其中包含一些解决方法。

答案 1 :(得分:0)

我不知道它是否仍然与您有关,但我也遇到类似的问题。我尝试将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进行检查),那么我们应该对其进行更新。

Credits

答案 2 :(得分:0)

我之前遇到过这个问题,因此我通过创建名为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);
    }