房间与条件的关系

Room relation with conditions

我有一个对象类别

@Entity
data class Category(
    @PrimaryKey
    val id: String,
    val name: String,
    val is_free: Boolean
)

和对象部分

@Entity
data class Section(
    @PrimaryKey
    val name: String,
    val category_ids: List<String>
)

类别对象的示例数据如下

[
  {
    "id": "quotes",
    "name": "General",
    "is_free": true
  },
  {
    "id": "favorites",
    "name": "Favorites",
    "is_free": true
  },
  {
    "id": "positivity",
    "name": "Positive Thinking",
    "is_free": false
  },
  {
    "id": "love",
    "name": "Love",
    "is_free": false
  }
]

这是 Section 对象的样本数据

[
  {
    "name": "Categories",
    "category_ids": [
      "quotes",
      "favorites"
    ]
  },
  {
    "name": "Most Popular",
    "category_ids": [
      "positivity",
      "love"
    ]
  }
]

现在我需要创建新对象作为 SectionWithCategory,方法是将 Section 对象的 category_ids 替换为相关的 Category 对象,

data class SectionWithCategories(
    val name: String,
    val categories: List<Category>
)

我的问题是如何创建 SectionWithCategories 对象以获得以下结果?

[
  {
    "name": "Categories",
    "categories": [
      {
        "id": "quotes",
        "name": "General",
        "is_free": true
      },
      {
        "id": "favorites",
        "name": "Favorites",
        "is_free": true
      }
    ]
  },
  {
    "name": "Most Popular",
    "categories": [
      {
        "id": "positivity",
        "name": "Positive Thinking",
        "is_free": false
      },
      {
        "id": "love",
        "name": "Love",
        "is_free": false
      }
    ]
  }
]

My problem is how can I create SectionWithCategories object to get following result?

有一些困难,因为假设您有适当的类型转换器,存储不利于关系的数据,从而提取相关数据。

您必须提取数据,然后从正在存储的 JOSN 字符串构建对象。

如果您没有类型转换器,那么您的部分实体将无法编译,因为您不能使用具有列表类型的列。

我建议,因为您似乎在“部分”和“类别”之间存在多对多关系,所以您采用第三个 table 来保持关系 (并消除需要有类型转换器).

  • 这也消除了将对象存储为 JSON
  • 所带来的膨胀

因此,不要在实体部分中使用 val category_ids: List<String>,例如:-

@Entity
data class Section(
    @PrimaryKey
    val id: String,
    val name: String,
    //val category_ids: List<String> use mapping/associatetive table instead
)
  • 已注释掉但可能应该删除。

您删除此行并拥有第三个实体,该实体存储部分的 ID 和允许任意组合的类别的相应 ID。以下是添加了建议选项(例如外键定义)的完整版本:-

@Entity(
    primaryKeys = ["sectionMap","categoryMap"] /* MUST have a primary key */

    /* Foreign Keys are optional, BUT enforce referential integrity */
    /* A Foreign Key is a rule that says that a child must have a parent */
    /* If the rule is broken then a Foreign Key conflict results */
    , foreignKeys = [
        /* Defining the Section's id as the parent and the SectionMap as the child */
        ForeignKey(
            entity = Section::class,
            parentColumns = ["id"],
            childColumns = ["sectionMap"],
            /* Optional within a Foreign Key are the following two Actions that can
                onDelete - action to be taken if the parent is deleted
                    CASCADE will delete the rows that are a child of the parent
                onUpdate - action taken if the value of the parent's mapped column changes
                    CASCADE will update the rows that are a child of the parent
             */
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        /* Defining the Category's id as the parent and the CategoryMap as the child */
        ForeignKey(
            entity = Category::class,
            parentColumns = ["id"],
            childColumns = ["categoryMap"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class SectionCategoryMap(
    val sectionMap: String,
    @ColumnInfo(index = true) /* Room warns if not indexed */
    val categoryMap: String
)
  • 添加了一些注释,作为评论,应该阅读并可能进一步调查。

现在您的 SectionWithCategories 可能是:-

data class SectionWithCategories(
    @Embedded
    val section: Section,
    @Relation(
        entity = Category::class, parentColumn = "id", entityColumn = "id",
        associateBy = Junction(
            value = SectionCategoryMap::class, /* The mapping/associative table Entity */
            parentColumn = "sectionMap", /* The column that maps to the parent (Section) */
            entityColumn = "categoryMap" /* The column that maps to the children (Categories) */
        )
    )
    val categories: List<Category>
)

演示

与@Dao class (AllDao) 如:-

@Dao
abstract class AllDao {
    @Insert(onConflict = IGNORE)
    abstract fun insert(category: Category): Long
    @Insert(onConflict = IGNORE)
    abstract fun insert(section: Section): Long
    @Insert(onConflict = IGNORE)
    abstract fun insert(sectionCategoryMap: SectionCategoryMap): Long

    @Transaction
    @Query("SELECT * FROM section")
    abstract fun getAllSectionWithCategories(): List<SectionWithCategories>
}
  • onConflict IGNORE 已针对插入进行编码,以便忽略重复项而不会发生异常,因此可以重新运行以下代码
  • Long 将是 rowid(通常隐藏的列),如果插入被忽略,它将是 -1。

假设使用了一个非常标准的@Database class(除了使用 allowMainThreadQueries,为了演示的简洁和方便),它是:-

@Database(
    entities = [
        Category::class,
        Section::class,
        SectionCategoryMap::class
    ],
    version = DBVERSION
)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDao(): AllDao
    companion object {
        const val DBVERSION = 1
        const val DBNAME = "my.db"
        @Volatile
        private var instance: TheDatabase? = null

        fun getInstance(context: Context): TheDatabase {
            if (instance == null) {
                instance = Room.databaseBuilder(
                    context,
                    TheDatabase::class.java,
                    DBNAME
                )
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}

然后在 activity 以下 :-

    db = TheDatabase.getInstance(this)
    dao = db.getAllDao();

    dao.insert(Category("quotes","General",true))
    dao.insert(Category("favorites","Favorites",true))
    dao.insert(Category("positivity","Positive Thinking", false))
    dao.insert(Category("love","Love",false))

    dao.insert(Section("Section1","Section1"))
    dao.insert(Section("Section2","Section2"))
    dao.insert(Section("Bonus","Bonus Section"))

    dao.insert(SectionCategoryMap("Section1","quotes"))
    dao.insert(SectionCategoryMap("Section1","favorites"))
    dao.insert(SectionCategoryMap("Section2","positivity"))
    dao.insert(SectionCategoryMap("Section2","love"))

    /* Extra Data */
    dao.insert(SectionCategoryMap("Bonus","love"))
    dao.insert(SectionCategoryMap("Bonus","favorites"))
    dao.insert(SectionCategoryMap("Bonus","positivity"))
    dao.insert(SectionCategoryMap("Bonus","quotes"))
    dao.insert(Section("Empty","Section with no Categories"))

    val TAG = "DBINFO"
    for(swc: SectionWithCategories in dao.getAllSectionWithCategories()) {
        Log.d(TAG,"Section is ${swc.section.name} ID is ${swc.section.id} categories are:-")
        for (c: Category in swc.categories) {
            Log.d(TAG,"\tCategory is ${c.name} ID is ${c.id} IS FREE is ${c.is_free}")
        }
    }
  • 根据您的数据插入类别
  • 插入部分,根据您的数据插入两个部分,另外两个部分
  • 插入反映您的数据的 SectionCategoryMap 行以及两个新部分的附加映射(奖励部分映射到所有类别,空白部分映射到无类别)。
  • 提取所有具有相应类别的部分作为 SectionWithCategories 对象并将结果写入日志。

输出到日志的结果为:-

2021-10-03 09:05:04.250 D/DBINFO: Section is Section1 ID is Section1 categories are:-
2021-10-03 09:05:04.250 D/DBINFO:   Category is Favorites ID is favorites IS FREE is true
2021-10-03 09:05:04.251 D/DBINFO:   Category is General ID is quotes IS FREE is true
2021-10-03 09:05:04.251 D/DBINFO: Section is Section2 ID is Section2 categories are:-
2021-10-03 09:05:04.251 D/DBINFO:   Category is Love ID is love IS FREE is false
2021-10-03 09:05:04.251 D/DBINFO:   Category is Positive Thinking ID is positivity IS FREE is false
2021-10-03 09:05:04.251 D/DBINFO: Section is Bonus Section ID is Bonus categories are:-
2021-10-03 09:05:04.251 D/DBINFO:   Category is Favorites ID is favorites IS FREE is true
2021-10-03 09:05:04.251 D/DBINFO:   Category is Love ID is love IS FREE is false
2021-10-03 09:05:04.251 D/DBINFO:   Category is Positive Thinking ID is positivity IS FREE is false
2021-10-03 09:05:04.251 D/DBINFO:   Category is General ID is quotes IS FREE is true
2021-10-03 09:05:04.251 D/DBINFO: Section is Section with no Categories ID is Empty categories are:-

如果你真的想要数据为 JSON 那么你可以使用 :-

    for(swc: SectionWithCategories in dao.getAllSectionWithCategories()) {
        Log.d(TAG, " SECTION JSON = ${Gson().toJson(swc.section)} CATEGROIES JSON = ${Gson().toJson(swc.categories)}")
    }

在这种情况下,输出将是:-

2021-10-03 09:24:34.954 D/DBINFO:  SECTION JSON = {"id":"Section1","name":"Section1"} CATEGROIES JSON = [{"id":"favorites","is_free":true,"name":"Favorites"},{"id":"quotes","is_free":true,"name":"General"}]
2021-10-03 09:24:34.956 D/DBINFO:  SECTION JSON = {"id":"Section2","name":"Section2"} CATEGROIES JSON = [{"id":"love","is_free":false,"name":"Love"},{"id":"positivity","is_free":false,"name":"Positive Thinking"}]
2021-10-03 09:24:34.960 D/DBINFO:  SECTION JSON = {"id":"Bonus","name":"Bonus Section"} CATEGROIES JSON = [{"id":"favorites","is_free":true,"name":"Favorites"},{"id":"love","is_free":false,"name":"Love"},{"id":"positivity","is_free":false,"name":"Positive Thinking"},{"id":"quotes","is_free":true,"name":"General"}]
2021-10-03 09:24:34.962 D/DBINFO:  SECTION JSON = {"id":"Empty","name":"Section with no Categories"} CATEGROIES JSON = []