【问题标题】:Room @Relation with composite Primary KeyRoom @Relation 与复合主键
【发布时间】:2018-10-25 07:39:47
【问题描述】:

我的问题是这个问题的延伸(也是我的:))-> Room composite Primary Key link to Foreign Key 所以,如果我有这个课程:

public class FoodWithIngredients extends Food{

    @Relation(parentColumn = "id", entityColumn = "food_id", entity = 
    Ingredient.class)
    private List<Ingredient> mIngredients;

}

但是“食物”表的PrimaryKeycomposite (primaryKeys = {"id", "language_id"})

如何使@Relation 返回"parentColumn = {"id", "language_id"}, entityColumn = {"food_id", food_language_id}" 的记录?

【问题讨论】:

  • 我遇到了同样的问题,我试图在两个表中创建一个 Index("food_id", "food_language_id", name = "relationHack") 链接它没有运气。唯一的想法是在父子实体中创建一个字段来连接复合键数据,尽管我不知道如何轻松维护该字段的数据。希望有帮助!!
  • 你找到解决办法了吗?
  • @marcosE。现在是2020年。你们有没有找到任何规范的解决方案?除了额外的现场解决方案(实际上并没有那么糟糕)和 Java/Kotlin 解决方案。

标签: android sqlite android-room composite-primary-key


【解决方案1】:

注解@Relation 仍然不提供对复合主键的支持。

获取查询多个表的数据以保持表清洁的最简单方法是使用 @Embedded 注释。如果您不介意变脏,您可以添加一个额外的字段,在其中连接主键的字段,并在该字段上使用 @Relation,这会带来维护字段的所有风险以及对其数据进行潜在错误比较的风险。可能值得,不知道对我来说是个坏主意。

所以清洁解决方案。提供了下一个表格。

//Multiple Staff and Machine can participate on a WorkOrder and they do hours of work related to it

@Entity
data class Staff(
        @PrimaryKey val jdeNumber: String,
        val idNfc: String,
        val staffDescription: String,
        val triadorNumber: String,
        val approverId: Int)

@Entity(primaryKeys = ["machineId"])
data class Machine(
        val machineId: String,
        val machineNumber: String,
        val machineDescription: String,
        val machineNumberAux: String,
        val manufacturer: String,
        val model: String,
        val productionNumber: String,
        val hasHours: Boolean)

//A WorkOrder may have staff, machine or both
@Entity
data class WorkOrder(
        @PrimaryKey val woId: String,
        val date: Long,
        val comments: String = "",
        val userId: String,
        val partStatus: Int
)

//Embedded annotation creates all the fields from the entity inside these tables and add to the field name a prefix, then when we join tables we have no name conflict
@Entity(
        primaryKeys = ["woIdStaff", "wo_jdeNumber"],
        foreignKeys = [
                ForeignKey(entity = WorkOrder::class,
                        parentColumns = ["woId"],
                        childColumns = ["woIdStaff"],
                        onUpdate = ForeignKey.CASCADE,
                        onDelete = ForeignKey.RESTRICT)]
)
data class WorkOrderStaff(
        val woIdStaff: String,
        @Embedded(prefix = "wo_")
        val staff: Staff,
        val hourFrom: Long,
        val hourTo: Long,
        val hoursUsed: Long
)

@Entity(
        primaryKeys = ["woIdMachine", "wo_machineId"],
        foreignKeys = [
                ForeignKey(entity = WorkOrder::class,
                        parentColumns = ["woId"],
                        childColumns = ["woIdMachine"],
                        onUpdate = ForeignKey.CASCADE,
                        onDelete = ForeignKey.RESTRICT)]
)
data class WorkOrderMachine(
        val woIdMachine: String,
        @Embedded(prefix = "wo_")
        val machine: Machine,
        val hourFromMachine: Long,
        val hourToMachine: Long,
        val hoursUsedMachine: Long
)

//Important this entity is the one that maps from JOIN queries
data class FullWorkOrder(
        @Embedded
        val workOrder: WorkOrder
        @Embedded
        val staff: WorkOrderStaff?
        @Embedded
        val machine: WorkOrderMachine?
)

然后我们要查询与在其中工作的员工和机器加入的所有工作订单以及每个工作订单的工作时间。所以我们在 Dao 中写了一个查询。

@Query("select * from WorkOrder LEFT JOIN WorkOrderStaff ON woId = woIdStaff LEFT JOIN WorkOrderMachine ON woId = woIdMachine")
abstract fun getAllFullWorkOrders(): List<FullWorkOrder>

当您测试 SQL 时,到实体 FullWorkOrder 的这种映射的行为类似于表可视化上的 Db 查询,您必须对其进行映射,以免重复数据行或分配不正确的数据,具体取决于连接的复杂性。我建议将数据移动到键值映射,然后加入所有过滤重复键。在这种情况下,我们将映射到我们在 UI 上使用的实体 -> DomainWorkOrder。

data class DomainWorkOrder(
    val id: String,
    .
    .
    .
    val staffList: List<StaffRow>
    val machineList: List<MachineRow>
)

我已经从示例中删除了我正在使用的表的真正复杂性,这就是为什么您在 SQL 上看不到任何复合 LEFT JOIN 的原因。我有 8 个表并入 WorkOrder(1-n),其中 2 个在其中嵌套了 1-n 关系。我保证这在大多数情况下都可以解决问题,如果您尝试将 Staff 表加入实体 FullWorkOrder 以获取最新数据,请小心,我对此有不好的体验。

我知道这不是纯粹的,但架构受到尊重,查询/映射过程不需要大量工作和维护。希望对你有帮助!!

【讨论】:

  • 谢谢分享。我也习惯于将多个字段中的 id 连接起来,这些字段共同提供了唯一性。
  • @ivan8m8 你试过嵌入式吗?您如何看待这种方法?我正在寻找更好的方法来实现这一点,我编写的大多数应用程序都必须支持离线模式。
  • marcos E. 考虑到 Room 的 @Relation 仍然不支持从复合外键到复合主键的引用,您的解决方案似乎没问题。但是,当您使用Map&lt;&gt;List&lt;FullWorkOrder&gt; 转到List&lt;DomainWorkOrder&gt;(对吗?)时,这有点怪怪的。顺便说一句,就我而言,我更改了架构,使其不再具有外键。在这种情况下,只需将一个小的父模型移动到其子模型中即可,因此删除了 2 个表中的 1 个。
  • @ivan8m8 这是将表作为任何关系数据库的标准连接表时从 SQL 中提取数据的方式,所以对我来说并不觉得 hacky,只是 Relation 为您完成了这项工作,仅此而已.您可以避免此问题并使用一些 SQL 技巧优化返回的行数(主要是子查询),这取决于您的查询,可能不值得。
【解决方案2】:

我找到了一些解决方法。但我想它会影响性能。 您需要在关系字段中添加特殊的 getter,这将使用复合主键的其他部分过滤结果。

在你的情况下,它看起来像:

public class FoodWithIngredients {

    @Embedded
    private Food food;

    @Relation(parentColumn = "id", entityColumn = "food_id", entity = 
    Ingredient.class)
    private List<Ingredient> mIngredients;

    public List<Ingredient> getIngredients() {
        List<Ingredient> result = List<Ingredient>();

        for (ingredient in mIngredients) {
            if (ingredient.foodLanguageId == food.languageId) {
                result.add(ingredient);   
            }
        }

        return result;
    }

}

【讨论】:

  • 这是我想到的解决方法,但它在大型数据集上的效率肯定非常低。你觉得效果如何?
  • @PatLee 我实际上不记得我在哪里使用它,但那是我找到的最佳解决方案。没有检查它在大型数据集上的工作方式,但当然需要一些时间来过滤结果。就我而言,它运行得足够顺利。
  • 他们在三周前实施了一种正确的方法,但它没有使用@Relation。看我的回答。
  • @PatLee 哇,不知道,谢谢分享!
【解决方案3】:

Room 2.4.0-alpha04 开始,您可以编写返回Map&lt;Entity, List&lt;Relation&gt;&gt; 用于1:N 关系的DAO 方法。这使您能够编写 JOIN 查询,指定如何获取关系,可选择指定 WHEREORDER BY 子句。

来源:https://issuetracker.google.com/issues/64247765

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 2018-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多