如何在 Room 中处理这种嵌套关系?

Posted

技术标签:

【中文标题】如何在 Room 中处理这种嵌套关系?【英文标题】:How to work this nested relationship in Room? 【发布时间】:2021-10-13 12:21:57 【问题描述】:

我需要一点帮助。 我已经创建了所有的表,我可以创建一个关系来检索应用程序,但我不知道如何检索具有模型的车辆品牌列表。

@Entity(tableName = "application_table", indices = [Index(value = ["name"], unique = true)])
data class ApplicationItem(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id") val id: Int? = 0,
    @ColumnInfo(name = "name") val name: String
)
data class ApplicationWithBrandAndModel(
    @Embedded val application: ApplicationItem,
    @Relation(
        entity = BrandOfVehicleItem::class,
        parentColumn = "userId",
        entityColumn = "brandId"
    )
    val brands: List<BrandWithModel>
)
data class BrandWithModel(
    @Embedded val brand: BrandOfVehicleItem,
    @Relation(
        parentColumn = "path",
        entityColumn = "brandCreatorId"
    )
    val models: List<ModelOfVehicleItem>
)

【问题讨论】:

【参考方案1】:

简而言之,您需要利用 AppBrandCrossRef 表来引用(associate)ApplicationItem 与 Brand 以获取 BrandWithModel 的列表。

Room 使用的关键字是 associateBy,因此在 @Relation 中您需要使用 associateBy 参数指定关联。

associateBy 参数本身采用 Junction 参数,您可以在其中定义交叉引用表和相应的列。

所以我相信你想要:-

data class ApplicationWithBrandAndModel(
    @Embedded val application: ApplicationItem,

    /*
    @Relation(
        entity = Brand::class,
        parentColumn = "userId", ??????? 
        entityColumn = "brandId"
    )

     */
    @Relation(
        entity = Brand::class,
        parentColumn = "id",
        entityColumn = "id",
        associateBy = Junction(
            value = ApplicationBrandCrossRef::class,
            parentColumn = "appId", // column in cross ref table that references the parent (application)
            entityColumn = "brandId" // column in cross ref table that references the child (brand)
        )
    )
    val brands: List<BrandWithModel>
)

以下是一个工作示例。

请注意,已经进行了更改,因为您有很长的引用字符串,并且您还拥有(如上所述)一个 userId,它不在您的图表中。

所以用来演示的实体是:-

型号

@Entity(
    indices = [Index("brandCreatorId", unique = false)],
    foreignKeys = [
        ForeignKey(
            entity = Brand::class,
            parentColumns = ["id"],
            childColumns = ["brandCreatorId"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE)
    ]
)
data class Model(
    @PrimaryKey
    val id: Long? = null,
    val path: String,
    val code: String,
    val value: String,
    val brandCreatorId: Long
)

品牌

@Entity(tableName = "brand_table")
data class Brand(
    @PrimaryKey
    val id: Long? = null,
    val path: String,
    val code: String,
    val value: String
)

应用程序项

@Entity(tableName = "application_table", indices = [Index(value = ["name"], unique = true)])
data class ApplicationItem(
    @PrimaryKey
    @ColumnInfo(name = "id") val id: Long? = null,
    @ColumnInfo(name = "name") val name: String
)

ApplicationBrandCrossRef

@Entity(
    primaryKeys = ["appId","brandId"],
    indices = [ Index(value = ["brandId"])],
    foreignKeys = [
        ForeignKey(
            entity = ApplicationItem::class,
            parentColumns = ["id"],
            childColumns = ["appId"],
            onDelete =  ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE),
        ForeignKey(
            entity = Brand::class,
            parentColumns = ["id"],
            childColumns = ["brandId"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class ApplicationBrandCrossRef(
    val appId: Long,
    val brandId: Long
)

BrandWithModel

data class BrandWithModel(
    @Embedded val brand: Brand,
    @Relation(
        parentColumn = "id",
        entityColumn = "brandCreatorId"
    )
    val models: List<Model>
)

ApplicationWithBrandAndModel

data class ApplicationWithBrandAndModel(
    @Embedded val application: ApplicationItem,

    /*
    @Relation(
        entity = Brand::class,
        parentColumn = "userId", ???????
        entityColumn = "brandId"
    )

     */
    @Relation(
        entity = Brand::class,
        parentColumn = "id",
        entityColumn = "id",
        associateBy = Junction(
            value = ApplicationBrandCrossRef::class,
            parentColumn = "appId", // column in cross ref table that references the parent (application)
            entityColumn = "brandId" // column in cross ref table that references the child (brand)
        )
    )
    val brands: List<BrandWithModel>
)

AllDao 中的 Dao 是:-

@Dao
abstract class AllDao 

    @Insert
    abstract fun insert(brand: Brand): Long
    @Insert
    abstract fun insert(model: Model): Long
    @Insert
    abstract fun insert(applicationItem: ApplicationItem): Long
    @Insert
    abstract fun insert(applicationBrandCrossRef: ApplicationBrandCrossRef)

    @Query("SELECT * FROM application_table")
    @Transaction
    abstract fun getApplicationItemWithBrandAndTheModels(): List<ApplicationWithBrandAndModel>

以下是用来测试的:-

    db = TheDatabase.getInstance(this)
    dao = db.getHymnDao()
    allDao = db.getAllDao()

    var ai1 = allDao.insert(ApplicationItem(name = "A1"))
    var ai2 = allDao.insert(ApplicationItem(name = "A2"))
    var ai3 = allDao.insert(ApplicationItem(name = "A3"))
    var ai4 = allDao.insert(ApplicationItem(name = "A4"))
    var b1 = allDao.insert(Brand(path = "patha", code = "codea", value = "vala"))
    var b2 = allDao.insert(Brand(path = "pathb",code = "codeb",value = "valb"))
    var b3 = allDao.insert(Brand(path = "pathc",code = "codec",value = "valc"))
    allDao.insert(Model(path = "ma",code = "ma",value = "ma",brandCreatorId = b1))
    allDao.insert(Model(path = "mb", code = "mb", value = "mb", brandCreatorId = b1))
    allDao.insert(Model(path = "mc",code = "mc", value = "mc",brandCreatorId = b2))
    allDao.insert(Model(path = "md",code = "md", value = "md", brandCreatorId = b2))
    allDao.insert(Model(path = "me", code = "me", value = "me", brandCreatorId = b2))
    allDao.insert(ApplicationBrandCrossRef(ai1,b2))
    allDao.insert(ApplicationBrandCrossRef(ai1,b3))
    allDao.insert(ApplicationBrandCrossRef(ai2,b1))
    allDao.insert(ApplicationBrandCrossRef(ai3,b1))
    allDao.insert(ApplicationBrandCrossRef(ai3,b2))
    allDao.insert(ApplicationBrandCrossRef(ai3,b3))
    for(a: ApplicationWithBrandAndModel in allDao.getApplicationItemWithBrandAndTheModels()) 
        Log.d(TAG,"AppItem is $a.application.name")
        for(b: BrandWithModel in a.brands) 
            Log.d(TAG,"\tBrand is $b.brand.code")
            for(m: Model in b.models) 
                Log.d(TAG,"\t\tModel is $m.code")
            
        
    

结果输出到日志:-

D/APPINFO: AppItem is A1
D/APPINFO:  Brand is codeb
D/APPINFO:      Model is mc
D/APPINFO:      Model is md
D/APPINFO:      Model is me
D/APPINFO:  Brand is codec
D/APPINFO: AppItem is A2
D/APPINFO:  Brand is codea
D/APPINFO:      Model is ma
D/APPINFO:      Model is mb
D/APPINFO: AppItem is A3
D/APPINFO:  Brand is codea
D/APPINFO:      Model is ma
D/APPINFO:      Model is mb
D/APPINFO:  Brand is codeb
D/APPINFO:      Model is mc
D/APPINFO:      Model is md
D/APPINFO:      Model is me
D/APPINFO:  Brand is codec
D/APPINFO: AppItem is A4
即预期结果

【讨论】:

太好了,感谢您的帮助,我还有一个问题,是否必须放置级联删除?如果我在 ApplicationBrandCrossRef 类中理解正确,如果 ApplicationBrandCrossRef 中的项目并删除应用程序并删除? onUpdate/onDelete 不是强制性的,但可能很有用 CASCADE 是最有可能有用的选项。见sqlite.org/foreignkeys.html#fk_actions。如果您觉得这个答案有用,请勾选它作为答案。

以上是关于如何在 Room 中处理这种嵌套关系?的主要内容,如果未能解决你的问题,请参考以下文章

如何在javascript中实现多个项目的计数?

Android Room:使用 Room 插入关系实体

如何将嵌套的json结构转换为数据框

如何在 Room 中声明 NUMERIC 字段

MySql - 查询如何编写嵌套条件?

当列名相同时,如何表示与 Android Room 的“多对多”关系?