1

我想与这两个数据类建立多对多关系,Member因为Team团队可以有多个成员,并且该成员可以在多个团队中

该类Member引用了团队 ID 作为 Map 中的键

data class Member(
    var id: String = "",
    var name: String = "",
    /** teamsPositionsMap   key -> teamId , value -> position */
    private var tPM: Map<String, String> = mapOf(),
)

并且Team通过 ID 列表对成员的 Class 引用并希望有一个MemberObjects 列表作为查询的结果

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 Room 建立这种关系(如果可能)

我考虑过复制每个Team以使每一行只有一个 memId 等等以进行Member迭代和展平,但我认为这不是最好的解决方案

4

1 回答 1

1

管理多对多关系的典型方法是使用一个中间表来映射关系。这样的表将有两列,每列标识关系的相应行。

例如,假设您有 id 为 1,2,3 .... 等的成员和团队 1000,1001,1002 等(1000+ 使用纯粹是为了便于区分这种解释)。

然后映射表我有如下行: -

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,
)
  • 注意注释掉的行

然后你有中间映射表(又名关联表,链接表....): -

@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(不是表)。

为方便起见,您使用 Room 注释 @Embedded 为父级和 @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>
)

和/或:-

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

各自的查询,只需要检索父级,Room 然后提取所有子级。因此,您可以在 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
于 2021-08-08T06:39:37.640 回答