OnConflictStrategy.REPLACE 会导致FOREIGN_KEY约束错误



我有以下道:

@Dao
public interface IncomeRecordWithTypeValueDao extends BaseDao<IncomeRecordWithTypeValue> {
@Query("SELECT * FROM IncomeRecordWithTypeValue")
Flowable<List<IncomeRecordWithTypeValue>> getAll();
}
@Dao
public interface BaseDao<T> {
@Insert(onConflict = OnConflictStrategy.REPLACE)
List<Long> synchronousInsertOrUpdate(List<T> objs);
}

我的 sqlite 表如下所示:

CREATE TABLE "IncomeRecordWithTypeValue" (
"id"    INTEGER NOT NULL,
"amount"    REAL NOT NULL,
"incomeRecordId"    INTEGER NOT NULL,
"incomeTypeId"  INTEGER NOT NULL,
FOREIGN KEY("incomeTypeId") REFERENCES "IncomeType"("id") ON DELETE CASCADE,
FOREIGN KEY("incomeRecordId") REFERENCES "IncomeRecord"("id") ON DELETE CASCADE,
PRIMARY KEY("id" AUTOINCREMENT)
)

基本上,我想要用于测试目的是在表中插入一行(如果它不存在),否则我想更新它。因此,我尝试使用以下语句更新所有现有行:

incomeRecordWithTypeValueDao().getAll().subscribe(all -> {
//all returns [IncomeRecordWithTypeValue{id=7, incomeRecordId=7, incomeTypeId=1, amount=55.0}, IncomeRecordWithTypeValue{id=8, incomeRecordId=8, incomeTypeId=1, amount=55.0}]
List<Long> long = incomeRecordWithTypeValueDao().synchronousInsertOrUpdate(all);
}, err -> {
err.printStackTrace();
//returns android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (Sqlite code 787 SQLITE_CONSTRAINT_FOREIGNKEY), (OS error - 2:No such file or directory)
});

我错过了什么吗?如果我单独使用插入和更新,它可能会起作用,但我的目标是在一个查询中使用这两个函数(OnConflict.REPLACE)

编辑:

我有以下触发器:

CREATE TRIGGER Trigger_DeleteIncomeRecAfterExpenseTypeDelete 
AFTER DELETE ON IncomeRecordWithTypeValue
BEGIN 
DELETE FROM IncomeRecord  
WHERE id = OLD.incomeRecordId AND NOT EXISTS(SELECT 1 FROM IncomeRecordWithTypeValue irwtv WHERE irwtv.incomeRecordId = IncomeRecord.id);  END

该触发器会导致问题,因为系统尝试删除收入记录,然后将值类型添加到由于触发器而已删除的同一记录中。

假设最终结果是 FK 约束不冲突,但暂时存在冲突,您可以使用延迟外键约束来应用更改。也就是说,约束检查将保留到事务提交为止。

对于创建 SQL,您将使用 :-

FOREIGN KEY("incomeTypeId") REFERENCES "IncomeType"("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY("incomeRecordId") REFERENCES "IncomeRecord"("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,

见 https://sqlite.org/foreignkeys.html#fk_deferred

对于实体,您将按照 https://developer.android.com/reference/androidx/room/ForeignKey#deferred() 进行编码deferred = true

额外的重新评论/编辑(触发器):-

我发现了引起问题的原因。问题是我有一个触发器(请参阅答案编辑)可以删除"收入记录",以防所有"值类型"被删除。因此,即使使用延迟也无法防止外键约束问题。也许删除触发器并在代码中实现触发器是要走的路。

这里的问题是,INSERT OR REPLACE通过删除要替换的行然后插入该行来工作,从而分配一个新的 rowid或其别名(整数主键,带或不带 AUTOINCREMENET aka autogenerate = true 将导致该列成为 rowid 的别名)。

如果不是使用 INSERT OR IGNORE (onConflict = IGNORE) 代替INSERT OR REPLACE(onConflict = REPLACEin room)并测试结果(Kotlin 中的 Long ),它将是一个大于 0(插入的行)或 -1(未插入的行)的正值。

在 -1(或 <1)的情况下,可以调用/运行 UPDATE 来执行现有行的更新,从而维护 id,也不会意外调用 TRIGGER。

  • 请注意,上述内容不会满足可以使用的负 rowid。但是,使用负 rowid 是必须强制的,并且超出了 SQLite 的正常使用范围。

相关内容

  • 没有找到相关文章

最新更新