问题描述
假设我有一个类别实体,其中只有两种类型(类别 A 和类别 B)。主键是一个复合键,由类别名称和一个布尔值组成,说明该类别是否属于类别 A(否则属于 B)。
一个 Item 可以同时属于这两种类别类型。因此,在我的项目实体中,我有两个外键(categoryAName 和 categoryBName),如果它们不属于任何类别,则它们都可能为空。如果仅隐式给出类别类型,是否可以将外键与其两个组合键映射到项目实体中?
我的代码如下:
@Entity(tableName = "category_table",primaryKeys = ["name","isCategoryA"])
data class CategoryEntity(val name: String,val isCategoryA: Boolean)
@Entity(
tableName = "item_table",foreignKeys = [ForeignKey(
entity = CategoryEntity::class,parentColumns = ["name","isCategoryA"],childColumns = ["categoryAName","???"],// how would one set the colum,if we don't want to save the value,when it's kNown implicitly?
onDelete = ForeignKey.SET_NULL,onUpdate = ForeignKey.CASCADE
),ForeignKey(
entity = CategoryEntity::class,childColumns = ["categoryBName",// isCategoryA would always be false
onDelete = ForeignKey.SET_NULL,onUpdate = ForeignKey.CASCADE
)]
)
data class ItemEntity(
@PrimaryKey val name: String,var categoryAName: String?,var categoryBName: String?
)
有没有办法实现这一点,而无需添加两个仅包含冗余信息的额外列?或者有没有更好的方法来实现这一点?
解决方法
或者有更好的方法来实现这一点吗?
我相信更好的方法是合并多对多关系,从而合并一个表(实体)来促进这一点。虽然我确实发现您提供的解释难以理解,因为您说类别属于一个类别。
也就是说,您有 2 个或 4 个类别(该示例假设每个类别有 4 个属于自己或另一个)。
一个项目似乎可以有 1、2、3 或 4 个类别。
因此你可以:-
- 类别的实体
- 项目的实体
- 以及用于将项目映射到它可能具有的 4 个(或 2 个)类别的实体。
所以:-
CategoryEntity 可以是:-
@Entity(tableName = "category_table",indices = [
Index(
value = ["name","isCategoryA"],unique = true
)
])
data class CategoryEntity(
@PrimaryKey(autoGenerate = true) val categoryId: Long,val name: String,val isCategoryA: Boolean
)
- 添加唯一标识类别的 id 列。
ItemEntity 可以是:-
@Entity(
tableName = "item_table"
)
data class ItemEntity(
@PrimaryKey val name: String
)
- 大大简化
ItemCategoryMap
@Entity(
primaryKeys = ["categoryId","itemName"],foreignKeys = [
ForeignKey(
entity = CategoryEntity::class,parentColumns = ["categoryId"],childColumns = ["categoryId"]
),ForeignKey(
entity = ItemEntity::class,parentColumns = ["name"],childColumns = ["itemName"]
)
]
)
data class ItemCategoryMap(
val categoryId: Long,val itemName: String
)
根据映射表将 Item 与相应类别组合在一起的 POJO ItemWithCategories :-
data class ItemWithCategories(
@Embedded
val item: ItemEntity,@Relation(
entity = CategoryEntity::class,entityColumn = "categoryId",parentColumn = "name",associateBy = Junction(
ItemCategoryMap::class,parentColumn = "itemName",entityColumn = "categoryId")
)
val categories: List<CategoryEntity>
)
阿道 AllDao :-
@Dao
interface AllDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(categoryEntity: CategoryEntity): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(itemEntity: ItemEntity): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(itemCategoryMap: ItemCategoryMap): Long
@Transaction
@Query("SELECT * FROM item_table")
fun getAllItemsWithCategories(): List<ItemWithCategories>
@Query("SELECT name FROM ITEM_TABLE WHERE rowid=:rowid")
fun getItemNameByRowid(rowid: Long): String
}
一个@Database TheDatabase :-
@Database(entities = [CategoryEntity::class,ItemEntity::class,ItemCategoryMap::class],version = 1,exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
public fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"mydb")
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
- 在主线程上运行演示的注意事项
最后一个 Activity 将它们放在一起并输出具有类别 (0-4) 的提取项目:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
val catATrueID =dao.insert(CategoryEntity(0,"CatA",true))
val catAFalseId = dao.insert(CategoryEntity(0,false))
val catBTrueId = dao.insert(CategoryEntity(0,"CatB",true))
val catBFalseId = dao.insert(CategoryEntity(0,false))
val catOoops = dao.insert(CategoryEntity(0,false))
val item1name = dao.getItemNameByRowid(dao.insert(ItemEntity("Item1")))
dao.insert(ItemCategoryMap(catATrueID,item1name))
dao.insert(ItemCategoryMap(catAFalseId,item1name))
dao.insert(ItemCategoryMap(catBTrueId,item1name))
dao.insert(ItemCategoryMap(catBFalseId,item1name))
val item2name = dao.getItemNameByRowid(dao.insert(ItemEntity("Item2")))
dao.insert(ItemCategoryMap(catBFalseId,item2name))
dao.insert(ItemCategoryMap(catATrueID,item2name))
val item3name = dao.getItemNameByRowid(dao.insert(ItemEntity("Item3")))
dao.insert(ItemCategoryMap(catBTrueId,item3name))
val item4name = dao.getItemNameByRowid(dao.insert(ItemEntity("item4")))
for(iwc: ItemWithCategories in dao.getAllItemsWithCategories()) {
Log.d("DBINFO","Item is ${iwc.item.name} has ${iwc.categories.size} categories." )
for (c: CategoryEntity in iwc.categories) {
Log.d("DBINFO","Category is ${c.name} ID is ${c.categoryId} IsCategoryA is ${c.isCategoryA}")
}
}
}
}
结果(仅为演示设计运行一次):-
2021-06-24 14:56:02.614 D/DBINFO: Item is Item1 has 4 categories.
2021-06-24 14:56:02.614 D/DBINFO: Category is CatA ID is 1 IsCategoryA is true
2021-06-24 14:56:02.614 D/DBINFO: Category is CatA ID is 2 IsCategoryA is false
2021-06-24 14:56:02.615 D/DBINFO: Category is CatB ID is 3 IsCategoryA is true
2021-06-24 14:56:02.615 D/DBINFO: Category is CatB ID is 4 IsCategoryA is false
2021-06-24 14:56:02.615 D/DBINFO: Item is Item2 has 2 categories.
2021-06-24 14:56:02.615 D/DBINFO: Category is CatB ID is 4 IsCategoryA is false
2021-06-24 14:56:02.615 D/DBINFO: Category is CatA ID is 1 IsCategoryA is true
2021-06-24 14:56:02.615 D/DBINFO: Item is Item3 has 1 categories.
2021-06-24 14:56:02.615 D/DBINFO: Category is CatB ID is 3 IsCategoryA is true
2021-06-24 14:56:02.615 D/DBINFO: Item is item4 has 0 categories.
附加
关于
如我的代码示例所示,一个项目可能只属于每个类型的一个类别。因此,该项目最多只能属于 2 个类别。此外,两个类别可能具有相同的名称,但仅限于它们属于不同的类型(这就是为什么有复合键的原因)。不应有两个名称相同、类型相同的类别。
然后您可以通过在类别名称项目名称组合上具有唯一索引来限制为唯一类别名称。因此,ItemCategoryMap 可以修改为:-
@Entity(
primaryKeys = ["categoryId",indices = [Index("itemName","categoryName",unique = true)] /* ADDED FOR ADDITIONAL */,val categoryName: String /* ADDED FOR ADDITIONAL */,val itemName: String
)
由于现在需要类别名称,因此需要类别名称,因为索引必须特定于单个表。因此,在插入 categoryName 时是必需的。
因此修改调用 (MainActivity) 以更改为使用(其余代码保持原样):-
val item1name = dao.getItemNameByRowid(dao.insert(ItemEntity("Item1")))
dao.insert(ItemCategoryMap(catATrueID,item1name))
dao.insert(ItemCategoryMap(catAFalseId,item1name))
dao.insert(ItemCategoryMap(catBTrueId,item1name))
dao.insert(ItemCategoryMap(catBFalseId,item1name))
val item2name = dao.getItemNameByRowid(dao.insert(ItemEntity("Item2")))
dao.insert(ItemCategoryMap(catBFalseId,item2name))
dao.insert(ItemCategoryMap(catATrueID,item2name))
val item3name = dao.getItemNameByRowid(dao.insert(ItemEntity("Item3")))
dao.insert(ItemCategoryMap(catBTrueId,item3name))
val item4name = dao.getItemNameByRowid(dao.insert(ItemEntity("item4")))
结果现在是:-
2021-06-25 10:01:22.671 D/DBINFO: Item is Item1 has 2 categories.
2021-06-25 10:01:22.671 D/DBINFO: Category is CatA ID is 1 IsCategoryA is true
2021-06-25 10:01:22.671 D/DBINFO: Category is CatB ID is 3 IsCategoryA is true
2021-06-25 10:01:22.671 D/DBINFO: Item is Item2 has 2 categories.
2021-06-25 10:01:22.672 D/DBINFO: Category is CatA ID is 1 IsCategoryA is true
2021-06-25 10:01:22.672 D/DBINFO: Category is CatB ID is 4 IsCategoryA is false
2021-06-25 10:01:22.672 D/DBINFO: Item is Item3 has 1 categories.
2021-06-25 10:01:22.672 D/DBINFO: Category is CatB ID is 3 IsCategoryA is true
2021-06-25 10:01:22.672 D/DBINFO: Item is item4 has 0 categories.
即:-
-
尝试使用
添加重复项(即仅 4 个类别行)val catOoops = dao.insert(CategoryEntity(0,false))
插入重复的 CatB false 被忽略,因此按照 :- -
Item1 只有第一个 CatA(即 CatA 为真,CatA 为假)被忽略。 CatB 也一样。
-
Item2 两者都有,因为插入两者时没有违反规则。
-
Item3 只有一个没有被破坏的规则。
-
项目没有。
如果您想获得带有关联项目的类别,那么您可以使用 CategoryWithItems :-
data class CategoryWithItems (
@Embedded
val category: CategoryEntity,@Relation(
entity = ItemEntity::class,entityColumn = "name",parentColumn = "categoryId",entityColumn = "itemName")
)
val items: List<ItemEntity>
)
还有一个 dao,例如 :-
@Query("SELECT * FROM category_table")
fun getAllCategoriesWithItems(): List<CategoryWithItems>
使用(使用上面加载的数据):-
for(cwi: CategoryWithItems in dao.getAllCategoriesWithItems()) {
Log.d("DBINFO","Category is ${cwi.category.name} isCategoryA is ${cwi.category.isCategoryA} id is ${cwi.category.categoryId},it has ${cwi.items.size} associated Items")
for(i: ItemEntity in cwi.items) {
Log.d("DBINFO","\tItem is ${i.name}")
}
}
结果是:-
2021-06-25 10:53:24.852 D/DBINFO: Category is CatA isCategoryA is true id is 1,it has 2 associated Items
2021-06-25 10:53:24.852 D/DBINFO: Item is Item1
2021-06-25 10:53:24.852 D/DBINFO: Item is Item2
2021-06-25 10:53:24.852 D/DBINFO: Category is CatA isCategoryA is false id is 2,it has 0 associated Items
2021-06-25 10:53:24.853 D/DBINFO: Category is CatB isCategoryA is true id is 3,it has 2 associated Items
2021-06-25 10:53:24.853 D/DBINFO: Item is Item1
2021-06-25 10:53:24.853 D/DBINFO: Item is Item3
2021-06-25 10:53:24.853 D/DBINFO: Category is CatB isCategoryA is false id is 4,it has 1 associated Items
2021-06-25 10:53:24.853 D/DBINFO: Item is Item2
当然可以使用 WHERE 子句。
注意/警告
代码中的使用val item1name = dao.getItemNameByRowid(dao.insert(ItemEntity("Item1")))
(等即通过rowid获取插入项的名称如果重新运行将导致失败,它的使用只是为了简化演示代码)。
上述解决方案还可以满足更多的需求,而不仅仅是 2 个类别名称,即类别名称没有限制(除了存储限制)。