【问题标题】:How to avoid room foreign key error - constraint failed (code 787)如何避免房间外键错误 - 约束失败(代码 787)
【发布时间】:2018-10-29 10:31:20
【问题描述】:

我有 3 个实体 - 1 个孩子和 2 个父母。 2 父实体可能有很多子实体,每个人都有自己的。

这是孩子:

@Entity(
        tableName = "share",
        foreignKeys = [
            ForeignKey(
                    entity = Pool::class,
                    childColumns = ["entity_id"],
                    parentColumns = ["id"],
                    onDelete = CASCADE
            ),
            ForeignKey(
                    entity = Note::class,
                    childColumns = ["entity_id"],
                    parentColumns = ["id"],
                    onDelete = CASCADE
            )
        ]
)
data class Share(

        @ColumnInfo(name = "share_id")
        @PrimaryKey(autoGenerate = false)
        val shareId: String,

        @ColumnInfo(name = "entity_id")
        val entityId: String,

        @ColumnInfo(name = "entityType")
        val entityType: Int
)

这是父母:

@Entity(tableName = "pool")
data class Pool(

        @PrimaryKey(autoGenerate = false)
        @ColumnInfo(name = "id")
        val poolId: String,

        @ColumnInfo(name = "category")
        val type: Int
)

@Entity(tableName = "note")
data class Note(

        @PrimaryKey(autoGenerate = false)
        @ColumnInfo(name = "id")
        val noteId: String
)

Pool 和 Note 可以有多个 Share,它们不相交,每个都有自己的独特之处。

但是当我尝试保存共享时,我遇到了下一个错误:

W/System.err: android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787)
W/System.err:     at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
W/System.err:     at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:783)
W/System.err:     at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
W/System.err:     at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
W/System.err:     at android.arch.persistence.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.java:50)
W/System.err:     at android.arch.persistence.room.EntityInsertionAdapter.insertAndReturnIdsList(EntityInsertionAdapter.java:243)
W/System.err:     at com.my_app.data.db.dao.share.ShareDao_Impl.insertShare(ShareDao_Impl.java:114)

如何避免这个错误?

【问题讨论】:

  • 对于 Share 对象,您如何分配 shareId?您是否在尝试插入之前验证了 entityId 已分配?
  • 我在一个事务中创建了保存父级(例如池)和共享。是的,他有正确的 id/entityId。当我添加新实体时抛出此错误 - 注意。在一切正常之前!

标签: android android-room


【解决方案1】:

您似乎正试图在同一列 (entityId) 上放置两个外键约束。奇怪的是,SQLite 将允许您使用此设置创建表。但是,当您添加新行时,它将检查其外键约束以验证该值是否存在于其他表中。因此,为了成功,您需要在两个表中都有 entityId:

Pool
1|pool1
2|pool2

Note 
1|note1

如果我创建一个 entityId = 1 的新共享,这将成功,因为我有一个 id=1 的池和一个 id=1 的便笺。

但如果我尝试使用 entityId = 2 创建共享,外部约束验证将失败,因为没有 id=2 的注释。

您需要重新考虑表的结构,以便同一列上没有多个外键,可能带有链接表。

您可以在 SQLite 中进行测试:

PRAGMA foreign_keys=true;

CREATE TABLE pool (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE note (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE share (id INTEGER PRIMARY KEY, entityId INTEGER, FOREIGN KEY(entityId) REFERENCES pool(id), FOREIGN KEY(entityId) REFERENCES note(id));

insert into pool (name) values ('pool1');
insert into pool (name) values ('pool2');
insert into note (name) values ('note1');

select * from pool;
1|pool1
2|pool2

select * from note;
1|note1

insert into share (entityId) values (1);

insert into share (entityId) values (2);
Error: FOREIGN KEY constraint failed

【讨论】:

  • 我试着做你写的查询。和以前我类似的测试请求一样——不管看起来多么奇怪,它们都能正常工作
  • 外键约束是否开启(sqlite> PRAGMA foreign_keys;)?如果回复为“0”,您需要将其打开 (sqlite> PRAGMA foreign_keys=true)。 Android 默认开启,但 sqlite3 不开启
【解决方案2】:

一个字段不能引用两个外键。在您的设置中,您声明“entity_id”是 Pool 类型的外键,父列是 Pool.id 并且“entity_id”是 Note 类型的外键,父列是 Note.id。这是一个无效的约束。

您需要在 Share 表中添加一个新列,该列将引用 Note 表作为外键。添加新字段,即字符串类型的“note_id”并将其注释为 Note 类的外键。像这样的:

@Entity(
    tableName = "share",
    foreignKeys = [
        ForeignKey(
                entity = Pool::class,
                childColumns = ["entity_id"],
                parentColumns = ["id"],
                onDelete = CASCADE
        ),
        ForeignKey(
                entity = Note::class,
                childColumns = ["note_id"],
                parentColumns = ["id"],
                onDelete = CASCADE
        )
    ]
)
data class Share(

    @ColumnInfo(name = "share_id")
    @PrimaryKey(autoGenerate = false)
    val shareId: String,

    @ColumnInfo(name = "entity_id")
    val entityId: String,

    @ColumnInfo(name = "entityType")
    val entityType: Int,

    @ColumnInfo(name = "note_id")
    val noteId: String
)

我不确定你的数据库的结构,但我不知道应用程序背后的想法,我无法评论结构。不过,我可以给你一个提示:如果可能的话,使用整数而不是字符串作为主键 - 它使数据库操作更快。

希望这个回答对你有帮助:)

【讨论】:

  • 谢谢回答。但我不明白为什么一个字段不能成为多个实体的外部键。我使用你的解决方案,但你能否分享一个链接或声明房间不支持这个 - 我在裸 sqlite 上试过 - 并且多个外键有效
  • 有可能(我不确定),但我不建议这样做。阅读有关数据库和规范化的更多信息,您将更好地理解为什么这种设置是不可取的。
猜你喜欢
  • 1970-01-01
  • 2019-10-26
  • 1970-01-01
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-09
相关资源
最近更新 更多