【问题标题】:Return type for Android Room joinsAndroid Room 加入的返回类型
【发布时间】:2017-12-17 00:21:52
【问题描述】:

假设我想在两个实体 FooBar 之间做一个 INNER JOIN

@Query("SELECT * FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
List<FooAndBar> findAllFooAndBar();

是否可以强制这样的返回类型?

public class FooAndBar {
    Foo foo;
    Bar bar;
}

当我尝试这样做时,我收到此错误:

error: Cannot figure out how to read this field from a cursor.

我也尝试过给表名加上别名以匹配字段名,但这也不起作用。

如果这是不可能的,我应该如何干净地构造一个兼容的返回类型,包括两个实体的所有字段?

【问题讨论】:

    标签: android android-room android-architecture-components


    【解决方案1】:

    是否可以强制这样的返回类型?

    您可以在foobar 上尝试@Embedded 注释。这将告诉 Room 尝试从您的查询中获取列并将它们倒入 foobar 实例中。我只对实体进行了尝试,但文档表明它也应该与 POJO 一起使用。

    但是,如果您的两个表具有相同名称的列,这可能无法正常工作。

    【讨论】:

    • 是的,这不起作用,因为我的实体有同名的列(如id)...
    • @pqvst:“我应该如何干净地构造一个兼容的返回类型,包括两个实体的所有字段?” -- 要么选择foobar@Embedded 并将其余字段直接放入FooAndBar,或者将所有字段直接放入FooAndBar。在 SQL 中使用AS 重命名结果集中的重复列,并根据需要使用@ColumnInfo 将那些AS-renamed 列映射到您想要的字段。
    • 这正是我想要避免做的事情......我觉得不是很“干净”:/
    • @pqvst:就目前而言,您的查询应该导致关于输出中重复列的 SQLite 错误,或者充其量是SQLiteDatabase 在幕后创建的Cursor 不会同时具有这两者id 列(以及任何其他重复的列)。您需要 SQL 查询中的所有列在结果集中具有不同的名称,否则 Cursor 将没有所有数据。解决此问题后,调整 FooBar 实体以匹配并使用 @Embedded 解决方案。
    • @pqvst: “我觉得不是很“干净””——然后摆脱 JOIN 并进行两次 DAO 调用,一个获取 Foo,另一个获取获取关联的Bar。 Room 的明确意图是任意查询导致任意 POJO 用于输出,就像您需要适当的 POJO 来进行改造调用一样。实体主要用于普通的 CRUD。
    【解决方案2】:

    @Query("SELECT * FROM Foo")
    List<FooAndBar> findAllFooAndBar();
    

    班级FooAndBar

    public class FooAndBar {
        @Embedded
        Foo foo;
    
        @Relation(parentColumn =  "Foo.bar_id", entityColumn = "Bar.id")
        List<Bar> bar;
        // If we are sure it returns only one entry
        // Bar bar;
    
        //Getter and setter...
    }
    

    这个解决方案似乎有效,但我并不为此感到自豪。 你怎么看?

    编辑:另一种解决方案

    Dao,我更喜欢明确选择,但“*”会完成这项工作:)

    @Query("SELECT Foo.*, Bar.* FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
    List<FooAndBar> findAllFooAndBar();
    

    班级FooAndBar

    public class FooAndBar {
        @Embedded
        Foo foo;
    
        @Embedded
        Bar bar;
    
        //Getter and setter...
    }
    

    编辑:从2.2.0-alpha01版本开始,房间@Relation注解可以管理一对一关系

    【讨论】:

    • 如果FooBar 之间存在冲突,我相信您可以通过创建后一个类的子集表示来消除这些冲突,例如public class BareBar { ...some bar fields... },然后将entity = BareBar.class 添加到@Relation。见:developer.android.com/reference/android/arch/persistence/room/…
    • 第二种解决方案导致“多个字段具有相同的columnName”编译错误然后实体具有名称相同的PK属性:id
    • 第二种解决方案真的有效吗?因为我收到“无法弄清楚如何转换光标...”错误。另外我正在使用@Embedded(prefix = "foo_") & @Embedded(prefix = "bar_")
    • @musooff 第二种解决方案仅在@Embeddeds 上没有设置prefix 值时对我有效。为了解决重复的列名,我必须为每个表上的每个类字段使用@ColumnInfo(name = ...)
    【解决方案3】:

    另一种选择是编写一个新的 POJO 来表示您的 JOIN 查询的结果结构(甚至支持列重命名以避免冲突):

    @Dao
    public interface FooBarDao {
       @Query("SELECT foo.field1 AS unique1, bar.field1 AS unique2 "
              + "FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
       public List<FooBar> getFooBars();
    
       static class FooBar {
           public String unique1;
           public String unique2;
       }
    }    
    

    见:room/accessing-data.html#query-multiple-tables

    【讨论】:

    • 这适用于具有相同名称的字段,只需要为它们有一个别名。
    【解决方案4】:

    试试这个方法。 例如,我在 ProductAttribute 之间有 M2M(多对多)关系。许多产品有许多属性,我需要通过Product.id获取所有属性并通过PRODUCTS_ATTRIBUTES.DISPLAY_ORDERING排序记录。

    |--------------|  |--------------|  |-----------------------|
    | PRODUCT      |  | ATTRIBUTE    |  | PRODUCTS_ATTRIBUTES   |
    |--------------|  |--------------|  |-----------------------|
    | _ID:  Long   |  | _ID: Long    |  | _ID: Long             |
    | NAME: Text   |  | NAME: Text   |  | _PRODUCT_ID: Long     |
    |______________|  |______________|  | _ATTRIBUTE_ID: Long   |
                                        | DISPLAY_ORDERING: Int |
                                        |_______________________|
    

    因此,模型将如下所示:

    @Entity(
        tableName = "PRODUCTS",
        indices = [
            Index(value = arrayOf("NAME"), unique = true)
        ]
    )
    class Product {
    
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = "_ID")
        var _id: Long = 0
    
        @ColumnInfo(name = "NAME")
        @SerializedName(value = "NAME")
        var name: String = String()
    
    }
    
    
    @Entity(
        tableName = "ATTRIBUTES",
        indices = [
            Index(value = arrayOf("NAME"), unique = true)
        ]
    )
    class Attribute {
    
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = "_ID")
        var _id: Long = 0
    
        @ColumnInfo(name = "NAME")
        @SerializedName(value = "NAME")
        var name: String = String()
    
    }
    

    “加入”表将是:

    @Entity(
        tableName = "PRODUCTS_ATTRIBUTES",
        indices = [
            Index(value = ["_PRODUCT_ID", "_ATTRIBUTE_ID"])
        ],
        foreignKeys = [
            ForeignKey(entity = Product::class, parentColumns = ["_ID"], childColumns = ["_PRODUCT_ID"]),
            ForeignKey(entity = Attribute::class, parentColumns = ["_ID"], childColumns = ["_ATTRIBUTE_ID"])
        ]
    )
    class ProductAttribute {
    
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = "_ID")
        var _id: Long = 0
    
        @ColumnInfo(name = "_PRODUCT_ID")
        var _productId: Long = 0
    
        @ColumnInfo(name = "_ATTRIBUTE_ID")
        var _attributeId: Long = 0
    
        @ColumnInfo(name = "DISPLAY_ORDERING")
        var displayOrdering: Int = 0
    
    }
    

    AttributeDAO 中,要获取基于Product._ID 的所有属性,您可以执行以下操作:

    @Dao
    interface AttributeDAO {
    
        @Query("SELECT ATTRIBUTES.* FROM ATTRIBUTES INNER JOIN PRODUCTS_ATTRIBUTES ON PRODUCTS_ATTRIBUTES._ATTRIBUTE_ID = ATTRIBUTES._ID INNER JOIN PRODUCTS ON PRODUCTS._ID = PRODUCTS_ATTRIBUTES._PRODUCT_ID WHERE PRODUCTS._ID = :productId ORDER BY PRODUCTS_ATTRIBUTES.DISPLAY_ORDERING ASC")
        fun getAttributesByProductId(productId: Long): LiveData<List<Attribute>>
    
    }
    

    如果您有任何问题,请告诉我。

    【讨论】:

    • 当两个表中的数据都超过 1000 时,此查询很可能会导致应用程序冻结。您能否建议我在查询增长和返回结果增长时如何避免冻结应用程序@dphans
    • @SureshMaidaragi 与分页库一起使用。现在将返回查询从LiveData&lt;List&lt;Attribute&gt;&gt; 更改为DataSource.Factory&lt;Int, Attribute&gt;。否则,使用查询中的LIMIT 页面大小。
    • 你为什么用@SerializedName
    • 我对 ManyToMany 和 RoomDB 有疑问:this one
    • @CoolMind 抱歉不需要注释 (@SerializedName) :D
    【解决方案5】:

    这是我的餐桌

    @Entity(tableName = "_food_table")
    data class Food(@PrimaryKey(autoGenerate = false)
                @ColumnInfo(name = "_food_id")
                var id: Int = 0,
                @ColumnInfo(name = "_name")
                var name: String? = "")
    

    这是我的购物车表和模型类(食品车)

    @Entity(tableName = "_client_cart_table")
    data class CartItem(
                    @PrimaryKey(autoGenerate = false)
                    @ColumnInfo(name = "_food_id")
                    var foodId: Int? = 0,
                    @Embedded(prefix = "_food")
                    var food: Food? = null,
                    @ColumnInfo(name = "_branch_id")
                    var branchId: Int = 0)
    

    注意:这里我们在两个表中看到 _food_id 列。它会抛出编译时错误。从@Embedded doc,您必须使用前缀来区分它们。

    道内

    @Query("select * from _client_cart_table inner join _food_table on _client_cart_table._food_id = _food_table._food_id where _client_cart_table._branch_id = :branchId")
    fun getCarts(branchId: Int) : LiveData<List<CartItem>>
    

    这个查询会返回这样的数据

    CartItem(foodId=5, food=Food(id=5, name=Black Coffee), branchId=1)
    

    我已经在我的项目中做到了这一点。所以试试吧。快乐编码

    【讨论】:

      猜你喜欢
      • 2019-04-10
      • 1970-01-01
      • 2020-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-29
      • 2021-06-20
      • 2020-08-06
      相关资源
      最近更新 更多