代码之家  ›  专栏  ›  技术社区  ›  M Rajoy

与Room&LiveData的多对多关系

  •  3
  • M Rajoy  · 技术社区  · 7 年前

    我有一个rest 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总是从本地房间数据库中检索返回的可流动项,并触发刷新操作来更新数据库和视图。

    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不能自己处理关系。我见过 this post 它只是从本地数据库中检索上一个城市列表,但本例与该列表不匹配。

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

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

    3 回复  |  直到 7 年前
        1
  •  7
  •   Nominalista    7 年前

    在房间里有可能有多对多的关系。

    第一次添加 @Ignore 对您的 Place 班级。它会告诉房间忽略这个属性,因为它不能保存没有转换器的对象列表。

    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)
    }
    

    最后一件重要的事情是每次插入新的 地点 .

    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
    }
    

    要查看更多详细信息,请参见 this article .

        2
  •  0
  •   Kevin Robatel    7 年前

    只是不要用同一个类 Entity 以及 Place 你从网络上获取的。

    把你的类逻辑和api结构联系在一起很难闻。

    从网络检索数据时,只需创建新的places实体并将其持久化到数据库。

        3
  •  0
  •   Pycpik    7 年前

    我有一个类似的用例。由于room无法管理关系,我在你提到的博客之后提出了这个解决方案:/

    @Entity
    data class Category(
        @PrimaryKey(autoGenerate = true)
        val id: Long = 0,
        var catId: String, 
        var name: String,
        @ForeignKey(entity = Place::class, parentColumns = ["id"], childColumns = ["placeId"], onDelete = ForeignKey.CASCADE)
        var placeId: String = ""
    )
    
    @Entity
    data class Place(
        @PrimaryKey
        var id: String,
        var name: String,
        @Ignore var categories: List<Category>
    )
    

    普莱托岛

    @Dao
    interface PlaceDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insert(place: Place)
    
        @Query("SELECT * FROM place WHERE id = :id")
        fun getPlace(id: String?): LiveData<Place>
    }
    
    fun AppDatabase.getPlace(placeId: String): LiveData<Place> {
        var placeLiveData = placeDao().getPlace(placeId)
        placeLiveData = Transformations.switchMap(placeLiveData, { place ->
            val mutableLiveData = MutableLiveData<Place>()
            Completable.fromAction { // cannot query in main thread
                place.categories = categoryDao().get(placeId)
            }
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe { mutableLiveData.postValue(place) }
            mutableLiveData
        })
        return placeLiveData
    }
    
    // run in transaction
    fun AppDatabase.insertOrReplace(place: Place) {
        placeDao().insert(place)
        place.categories?.let {
            it.forEach {
                it.placeId = place.id
            }
            categoryDao().delete(place.id)
            categoryDao().insert(it)
        }
    }
    

    分类岛

    @Dao
    interface CategoryDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insert(categories: List<Category>)
    
        @Query("DELETE FROM category WHERE placeId = :placeId")
        fun delete(placeId: String?)
    
        @Query("SELECT * FROM category WHERE placeId = :placeId")
        fun get(placeId: String?): List<Category>
    }
    

    不是什么大粉丝,但我暂时没有找到更好的方法。