多对多关系(复杂数据中的 ID)Android 房间

Many To Many Relationship (IDs in complex data) Android Room

我想用这个拖车数据实现多对多关系 类 MemberTeam 因为团队可以有多个成员并且成员可以在多个团队中

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