如何在Room数据库(Kotlin)中用外键关联多张表?及报错排查
Kotlin Room多表外键关联修正方案
错误原因分析
SQLite外键约束明确要求:被引用的父表字段必须是主键或带有唯一索引。你当前的问题是FieldTypeModel的外键错误引用了FieldModel的field_type字段,该字段既不是主键也无唯一约束,同时表结构的关联逻辑不符合字典表的设计规范——字段类型表应该是被字段表引用,而非反过来。
正确表结构修正
1. 调整关联逻辑(推荐方案)
FieldTypeModel作为字段类型字典表,应该被FieldModel引用:即FieldModel的字段关联FieldTypeModel的主键id,而非反向关联。修正后的实体类代码如下:
/* 表1:文档类型表 */ @Entity(tableName = "Doc_Type_Table") data class DocTypeModel( @ColumnInfo(name = "title") var title: String, @ColumnInfo(name = "date") var date: String, @ColumnInfo(name = "color_code") var colorCode: String, ) { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Int = 0 } /* 表2:字段类型字典表(先定义,方便被引用) */ @Entity(tableName = "Field_Type_Table") data class FieldTypeModel( @ColumnInfo(name = "type") var fieldType: String // 存储字段类型名称,比如"文本"、"数字"等 ) { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Int = 0 // 主键作为被引用的唯一标识 } /* 表3:字段表,关联文档类型和字段类型 */ @Entity( tableName = "Field_Table", foreignKeys = [ // 关联文档类型表 ForeignKey( entity = DocTypeModel::class, childColumns = ["doc_type_id"], parentColumns = ["id"], onDelete = ForeignKey.CASCADE ), // 关联字段类型表 ForeignKey( entity = FieldTypeModel::class, childColumns = ["field_type_id"], parentColumns = ["id"], onDelete = ForeignKey.CASCADE ) ], // 可选:给外键字段加索引,提升查询性能 indices = [Index(value = ["doc_type_id"]), Index(value = ["field_type_id"])] ) data class FieldModel( @ColumnInfo(name = "doc_type_id") var docTypeId: Int, @ColumnInfo(name = "field_type_id") var fieldTypeId: Int, // 关联字段类型表的主键 @ColumnInfo(name = "name") var name: String, @ColumnInfo(name = "value") var value: String, ) { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Int = 0 }
2. 若坚持原有关联方向(不推荐)
如果因特殊需求必须让FieldTypeModel引用FieldModel的field_type字段,需给FieldModel的field_type添加唯一索引,代码修改如下:
@Entity( tableName = "Field_Table", foreignKeys = [ ForeignKey( entity = DocTypeModel::class, childColumns = ["doc_type_id"], parentColumns = ["id"], onDelete = ForeignKey.CASCADE ) ], // 添加唯一索引 indices = [Index(value = ["field_type"], unique = true)] ) data class FieldModel( @ColumnInfo(name = "doc_type_id") var docTypeId: Int, @ColumnInfo(name = "name") var name: String, @ColumnInfo(name = "field_type") var fieldType: Int, @ColumnInfo(name = "value") var value: String, ) { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Int = 0 }
注意:此方案会限制
FieldModel中每个field_type值只能存在一条记录,不符合常规业务逻辑——同一字段类型应允许对应多个字段,因此不推荐使用。
关联数据查询示例
如果需要查询包含关联数据的结果,可通过Room的@Embedded和@Relation定义数据类:
// 文档类型包含其下所有字段,每个字段包含对应的类型信息 data class DocTypeWithFields( @Embedded val docType: DocTypeModel, @Relation( parentColumn = "id", entityColumn = "doc_type_id", entity = FieldModel::class ) val fields: List<FieldWithType> ) data class FieldWithType( @Embedded val field: FieldModel, @Relation( parentColumn = "field_type_id", entityColumn = "id", entity = FieldTypeModel::class ) val fieldType: FieldTypeModel )
在Dao中定义查询方法:
@Dao interface DocDao { @Transaction @Query("SELECT * FROM Doc_Type_Table") suspend fun getAllDocTypesWithFields(): List<DocTypeWithFields> }
内容的提问来源于stack exchange,提问作者Fahad
相关产品推荐
相关产品推荐

