OnConflictStrategy.REPLACE 导致 FOREIGN_KEY 约束错误

OnConflictStrategy.REPLACE causes a FOREIGN_KEY Constraint error

我有以下 dao:

@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 table 看起来像这样:

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)
)

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

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 约束不冲突,但暂时冲突,方法是使用 延迟外键约束.即在事务提交之前保留约束检查。

对于 CREATE 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

额外的 comment/edit (TRIGGER) :-

I found what raises the issue. The issue is that I have a trigger (see answer edit) that removes the 'incomerecord' in case all 'valuetypes' get deleted. so even using deferred won't prevent the foreign key constraint issue unfourtantly. Maybe deleting the trigger and implementing the trigger in code is the way to go.

这里的问题是 INSERT OR REPLACE 的工作方式是 删除要替换的行,然后插入行 ,从而分配 一个新的 rowid 或其别名 (INTEGER PRIMARY KEY,带或不带 AUTOINCREMENET aka autogenerate = true 将导致该列成为 rowid 的别名)

If 而不是 INSERT OR REPLACEonConflict = REPLACE in room)使用 INSERT OR IGNORE (onConflict = IGNORE) 并测试结果(Kotlin 中的 Long ),这将是大于 0(插入的行)或 -1(未插入的行)的正值。

在 -1(或 < 1)的情况下,然后更新可以是 invoked/run 以执行现有行的更新,从而维护 id 并且也不会意外调用 TRIGGER。

  • 请注意,以上内容不适合可以使用的负 rowid。但是,使用负 rowid 是必须强制执行的,并且超出 SQLite.
  • 的正常使用范围