首页 > 解决方案 > 与 Room 和 LiveData 的多对多关系

问题描述

我有一个休息 api,它返回一个地点列表,其中有一个类别列表:

{
      "id": "35fds-45sdgk-fsd87",
      "name" : "My awesome place",
       "categories" : [
          {
            "id": "cat1",
            "name" : "Category 1"
          },
          {
            "id": "cat2",
            "name" : "Category 2"
          },
          {
            "id": "cat3",
            "name" : "Category 3"
          }
       ]
}

因此,使用改造我从远程服务器获得这些模型类:

data class Category(var id: String, var name: String)

data class Place(
  var id: String,
  var name: String,
  var categories: List<Category>
)

问题是——我希望 viewModel 总是从返回 Flowables 的本地 Room 数据库中检索,并且只触发刷新操作来更新数据库,从而更新视图。

DAO方法示例:

@Query("select * from Places where placeId = :id")
fun getPlace(id: String): Flowable<Place>

所以我尝试像这样对这两个类进行建模:

@Entity
data class Category(var id: String, var name: String)


@Entity
data class Place(
  @PrimaryKey
  var id: String,
  var name: String,
  var categories: List<Category>
)

但是,Room 当然不能自己处理关系。我看过这篇文章,它只是从本地数据库中检索以前的城市列表,但这种情况与那个不匹配。

我能想到的唯一选择是将数据库中的类别保存为 JSON 字符串,但这会失去数据库的关系质量......

这似乎是一个非常常见的用例,但我没有找到太多关于它的信息。

标签: androidretrofitrx-java2android-roomandroid-livedata

解决方案


Room 中可能存在多对多关系。

首先@Ignore为您的课程添加注释Place。它将告诉 Room 忽略此属性,因为它无法在没有转换器的情况下保存对象列表。

data class Category(
        @PrimaryKey var id: String, 
        var name: String
)

data class Place(
        @PrimaryKey var id: String,
        var name: String,
        @Ignore var categories: List<Category>
) 

然后创建一个类来表示这两个类之间的连接。

@Entity(primaryKeys = ["place_id", "category_id"],
        indices = [
            Index(value = ["place_id"]),
            Index(value = ["category_id"])
        ],
        foreignKeys = [
            ForeignKey(entity = Place::class,
                    parentColumns = ["id"],
                    childColumns = ["place_id"]),
            ForeignKey(entity = Category::class,
                    parentColumns = ["id"],
                    childColumns = ["category_id"])
        ])
data class CategoryPlaceJoin(
        @ColumnInfo(name = "place_id") val placeId: String,
        @ColumnInfo(name = "category_id") val categoryId: String
)

如您所见,我使用了外键。

现在你可以指定特殊的 DAO 来获取一个地方的类别列表。

@Dao
interface PlaceCategoryJoinDao {

    @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
    @Query("""
        SELECT * FROM category INNER JOIN placeCategoryJoin ON
        category.id = placeCategoryJoin.category_id WHERE
        placeCategoryJoin.place_id = :placeId
        """)
    fun getCategoriesWithPlaceId(placeId: String): List<Category>

    @Insert
    fun insert(join: PlaceCategoryJoin)
}

最后一件重要的事情是每次插入 new 时插入 join 对象Place

val id = placeDao().insert(place)
for (place in place.categories) {
   val join = CategoryPlaceJoin(id, category.id)
   placeCategoryJoinDao().insert(join)
}

现在,当您从placeDao()他们那里获得空类别列表时。为了添加类别,您可以使用这部分代码:

fun getPlaces(): Flowable<List<Place>> {
    return placeDao().getAll()
            .map { it.map { place -> addCategoriesToPlace(place) } }
}

private fun addCategoriesToPlace(place: Place): Place {
    place.categories = placeCategoryJoinDao().getCategoriesWithPlaceId(place.id)
    return place
}

要查看更多详细信息,请参阅这篇文章


推荐阅读