多对多关系(复杂数据中的 ID)Android 房间
Many To Many Relationship (IDs in complex data) Android Room
我想用这个拖车数据实现多对多关系 类 Member
和 Team
因为团队可以有多个成员并且成员可以在多个团队中
Member
Class 引用团队 ID 作为 Map
中的键
data class Member(
var id: String = "",
var name: String = "",
/** teamsPositionsMap key -> teamId , value -> position */
private var tPM: Map<String, String> = mapOf(),
)
并且 Team
Class 通过 ID 列表引用成员并且希望有一个 Member
对象列表作为查询结果
data class Team(
var id: String = "",
var name: String = "",
var memIds: List<String> = listOf(),
/** get it by query memId */
var memberList: List<Member>? = null,
)
我的问题是:我如何通过单一查询(如果可能)Android 与 Android 房间建立这种关系
我考虑过复制每个 Team
以使每一行只有一个 memId 等等 Member
迭代和展平,但我认为这不是最好的解决方案
管理 many-many 关系的典型方法是使用中间 table 来映射关系。这样的 table 将有两列,每列标识关系的相应行。
例如假设您有 ID 为 1、2、3 .... 等的成员和团队 1000、1001、1002 等等(使用 1000+ 纯粹是为了便于区分此解释)。
然后映射 table 我有这样的行:-
1000,1
1000,3
1000,5
1001,2
1001,4
1002,1
1002,2
1002,3
1002,4
1002,5
所以 1000 标识的团队有 1,3 和 5 标识的成员,1001 标识的团队有 2 和 3 标识的成员,1002 有 1 到 5 标识的成员。
要在 Room 中实现此功能,您需要拥有核心实体成员和团队,而无需考虑它们之间的关联,因此 :-
@Entity
data class Member(
@PrimaryKey
var id: String = "",
var name: String = "",
/** teamsPositionsMap key -> teamId , value -> position */
//private var tPM: Map<String, String> = mapOf(),
)
和
@Entity
data class Team(
@PrimaryKey
var id: String = "",
var name: String = ""
//var memIds: List<String> = listOf(),
/** get it by query memId */
//var memberList: List<Member>? = null,
)
- 注意注释掉的行
然后你有中间映射table(又名关联table,link table ....):-
@Entity(
primaryKeys = ["memberIdMap","teamIdMap"],
indices = [Index(value = ["teamIdMap"], unique = false)],
foreignKeys = [
ForeignKey(
entity = Member::class,
parentColumns = ["id"],
childColumns = ["memberIdMap"],
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Team::class,
parentColumns = ["id"],
childColumns = ["teamIdMap"],
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
)
]
)
data class MemberTeamMap(
var memberIdMap: String,
var teamIdMap: String
)
- Room 需要一个 PRIMARY KEY,复合主键已被定义为 PRIMARY KEY 的必要条件是它包含 UNIQUE 值,因此仅将任一列作为主键将不允许多个。
- 另一列的索引不是必需的,但如果省略,房间会发出警告。
- 不需要外键,但它们确实强制执行参照完整性,即映射不包含孤儿。
要真正让成员与他们的团队或团队与成员一起,您需要一个 POJO(不是 table),它具有 parent(成员或团队)和 list/array(成员为团队,团队为成员)。
为方便起见,您对 parent 使用 Room 注释 @Embedded,对 children 使用 @Relation。
所以你可以:-
data class TeamWithMembers(
@Embedded
var team: Team,
@Relation(
entity = Member::class, parentColumn = "id", entityColumn = "id",
associateBy = Junction(
value = MemberTeamMap::class, parentColumn = "teamIdMap", entityColumn = "memberIdMap"
)
)
var members: List<Member>
)
and/or:-
data class MemberWithTeams (
@Embedded
var member: Member,
@Relation(
entity = Team::class, parentColumn = "id", entityColumn = "id",
associateBy = Junction(
MemberTeamMap::class,parentColumn = "memberIdMap", entityColumn = "teamIdMap"
)
)
var teams: List<Team>
)
各自的查询,只要检索到parent,Room便提取出所有的children。因此,您可以在 a/your Dao/s 中编写以下代码:-
@Insert
abstract fun insert(member: Member): Long
@Insert
abstract fun insert(team: Team): Long
@Insert
abstract fun insert(memberTeamMap: MemberTeamMap): Long
@Query("SELECT * FROM member")
@Transaction
abstract fun getAllMembersWithTeams(): List<MemberWithTeams>
@Query("SELECT * FROM team")
@Transaction
abstract fun getAllTeamsWithMember(): List<TeamWithMembers>
将上述内容付诸实践进行演示,请考虑以下内容:-
var tag = "TEAMDBINFO"
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
// Add some members and teams
dao.insert(Member(id = "M1",name = "Member1"))
dao.insert(Member(id = "M2", name = "Member2"))
dao.insert(Member(id = "M3", name = "Member3"))
dao.insert(Member(id = "M4", name = "Member4"))
dao.insert(Member(id = "M5", name = "Member5"))
dao.insert(Team(id = "T1", name = "Team1"))
dao.insert(Team(id = "T2", name = "Team2"))
dao.insert(Team(id = "T3",name = "Team3"))
dao.insert(Team(id = "T4",name = "Team4"))
// do the mapping
dao.insert(MemberTeamMap("M1","T1"))
dao.insert(MemberTeamMap("M3","T1"))
dao.insert(MemberTeamMap("M5","T1"))
dao.insert(MemberTeamMap("M2","T2"))
dao.insert(MemberTeamMap("M4","T2"))
dao.insert(MemberTeamMap("M1","T3"))
dao.insert(MemberTeamMap("M2","T3"))
dao.insert(MemberTeamMap("M3","T3"))
dao.insert(MemberTeamMap("M4","T3"))
dao.insert(MemberTeamMap("M5","T3"))
// Extract the Teams and their members :-
for(twm: TeamWithMembers in dao.getAllTeamsWithMember()) {
Log.d(tag,"Team is ${twm.team.name}")
for(m: Member in twm.members) {
Log.d(tag,"\tMember is ${m.name}")
}
}
如果以上是 运行 那么日志将包括:-
D/TEAMDBINFO: Team is Team1
D/TEAMDBINFO: Member is Member1
D/TEAMDBINFO: Member is Member3
D/TEAMDBINFO: Member is Member5
D/TEAMDBINFO: Team is Team2
D/TEAMDBINFO: Member is Member2
D/TEAMDBINFO: Member is Member4
D/TEAMDBINFO: Team is Team3
D/TEAMDBINFO: Member is Member1
D/TEAMDBINFO: Member is Member2
D/TEAMDBINFO: Member is Member3
D/TEAMDBINFO: Member is Member4
D/TEAMDBINFO: Member is Member5
D/TEAMDBINFO: Team is Team4
我想用这个拖车数据实现多对多关系 类 Member
和 Team
因为团队可以有多个成员并且成员可以在多个团队中
Member
Class 引用团队 ID 作为 Map
data class Member(
var id: String = "",
var name: String = "",
/** teamsPositionsMap key -> teamId , value -> position */
private var tPM: Map<String, String> = mapOf(),
)
并且 Team
Class 通过 ID 列表引用成员并且希望有一个 Member
对象列表作为查询结果
data class Team(
var id: String = "",
var name: String = "",
var memIds: List<String> = listOf(),
/** get it by query memId */
var memberList: List<Member>? = null,
)
我的问题是:我如何通过单一查询(如果可能)Android 与 Android 房间建立这种关系
我考虑过复制每个 Team
以使每一行只有一个 memId 等等 Member
迭代和展平,但我认为这不是最好的解决方案
管理 many-many 关系的典型方法是使用中间 table 来映射关系。这样的 table 将有两列,每列标识关系的相应行。
例如假设您有 ID 为 1、2、3 .... 等的成员和团队 1000、1001、1002 等等(使用 1000+ 纯粹是为了便于区分此解释)。
然后映射 table 我有这样的行:-
1000,1
1000,3
1000,5
1001,2
1001,4
1002,1
1002,2
1002,3
1002,4
1002,5
所以 1000 标识的团队有 1,3 和 5 标识的成员,1001 标识的团队有 2 和 3 标识的成员,1002 有 1 到 5 标识的成员。
要在 Room 中实现此功能,您需要拥有核心实体成员和团队,而无需考虑它们之间的关联,因此 :-
@Entity
data class Member(
@PrimaryKey
var id: String = "",
var name: String = "",
/** teamsPositionsMap key -> teamId , value -> position */
//private var tPM: Map<String, String> = mapOf(),
)
和
@Entity
data class Team(
@PrimaryKey
var id: String = "",
var name: String = ""
//var memIds: List<String> = listOf(),
/** get it by query memId */
//var memberList: List<Member>? = null,
)
- 注意注释掉的行
然后你有中间映射table(又名关联table,link table ....):-
@Entity(
primaryKeys = ["memberIdMap","teamIdMap"],
indices = [Index(value = ["teamIdMap"], unique = false)],
foreignKeys = [
ForeignKey(
entity = Member::class,
parentColumns = ["id"],
childColumns = ["memberIdMap"],
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Team::class,
parentColumns = ["id"],
childColumns = ["teamIdMap"],
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
)
]
)
data class MemberTeamMap(
var memberIdMap: String,
var teamIdMap: String
)
- Room 需要一个 PRIMARY KEY,复合主键已被定义为 PRIMARY KEY 的必要条件是它包含 UNIQUE 值,因此仅将任一列作为主键将不允许多个。
- 另一列的索引不是必需的,但如果省略,房间会发出警告。
- 不需要外键,但它们确实强制执行参照完整性,即映射不包含孤儿。
要真正让成员与他们的团队或团队与成员一起,您需要一个 POJO(不是 table),它具有 parent(成员或团队)和 list/array(成员为团队,团队为成员)。
为方便起见,您对 parent 使用 Room 注释 @Embedded,对 children 使用 @Relation。
所以你可以:-
data class TeamWithMembers(
@Embedded
var team: Team,
@Relation(
entity = Member::class, parentColumn = "id", entityColumn = "id",
associateBy = Junction(
value = MemberTeamMap::class, parentColumn = "teamIdMap", entityColumn = "memberIdMap"
)
)
var members: List<Member>
)
and/or:-
data class MemberWithTeams (
@Embedded
var member: Member,
@Relation(
entity = Team::class, parentColumn = "id", entityColumn = "id",
associateBy = Junction(
MemberTeamMap::class,parentColumn = "memberIdMap", entityColumn = "teamIdMap"
)
)
var teams: List<Team>
)
各自的查询,只要检索到parent,Room便提取出所有的children。因此,您可以在 a/your Dao/s 中编写以下代码:-
@Insert
abstract fun insert(member: Member): Long
@Insert
abstract fun insert(team: Team): Long
@Insert
abstract fun insert(memberTeamMap: MemberTeamMap): Long
@Query("SELECT * FROM member")
@Transaction
abstract fun getAllMembersWithTeams(): List<MemberWithTeams>
@Query("SELECT * FROM team")
@Transaction
abstract fun getAllTeamsWithMember(): List<TeamWithMembers>
将上述内容付诸实践进行演示,请考虑以下内容:-
var tag = "TEAMDBINFO"
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
// Add some members and teams
dao.insert(Member(id = "M1",name = "Member1"))
dao.insert(Member(id = "M2", name = "Member2"))
dao.insert(Member(id = "M3", name = "Member3"))
dao.insert(Member(id = "M4", name = "Member4"))
dao.insert(Member(id = "M5", name = "Member5"))
dao.insert(Team(id = "T1", name = "Team1"))
dao.insert(Team(id = "T2", name = "Team2"))
dao.insert(Team(id = "T3",name = "Team3"))
dao.insert(Team(id = "T4",name = "Team4"))
// do the mapping
dao.insert(MemberTeamMap("M1","T1"))
dao.insert(MemberTeamMap("M3","T1"))
dao.insert(MemberTeamMap("M5","T1"))
dao.insert(MemberTeamMap("M2","T2"))
dao.insert(MemberTeamMap("M4","T2"))
dao.insert(MemberTeamMap("M1","T3"))
dao.insert(MemberTeamMap("M2","T3"))
dao.insert(MemberTeamMap("M3","T3"))
dao.insert(MemberTeamMap("M4","T3"))
dao.insert(MemberTeamMap("M5","T3"))
// Extract the Teams and their members :-
for(twm: TeamWithMembers in dao.getAllTeamsWithMember()) {
Log.d(tag,"Team is ${twm.team.name}")
for(m: Member in twm.members) {
Log.d(tag,"\tMember is ${m.name}")
}
}
如果以上是 运行 那么日志将包括:-
D/TEAMDBINFO: Team is Team1
D/TEAMDBINFO: Member is Member1
D/TEAMDBINFO: Member is Member3
D/TEAMDBINFO: Member is Member5
D/TEAMDBINFO: Team is Team2
D/TEAMDBINFO: Member is Member2
D/TEAMDBINFO: Member is Member4
D/TEAMDBINFO: Team is Team3
D/TEAMDBINFO: Member is Member1
D/TEAMDBINFO: Member is Member2
D/TEAMDBINFO: Member is Member3
D/TEAMDBINFO: Member is Member4
D/TEAMDBINFO: Member is Member5
D/TEAMDBINFO: Team is Team4