【问题标题】:Many To Many Relationship (IDs in complex data) Android Room多对多关系(复杂数据中的ID)Android Room
【发布时间】:2021-08-07 23:35:05
【问题描述】:

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

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 列表对成员的引用并希望得到一个 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 Room 建立这种关系

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

【问题讨论】:

    标签: android many-to-many android-room android-room-relation


    【解决方案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(不是表),它具有父级(成员或团队)和列表/数组(成员的团队和团队的成员) )。

    为了方便这一点,您可以为父级使用房间注释 @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
    

    【讨论】:

    • 我可以忽略房间中被注释掉的字段并用它们填充映射表,对吧?
    • @HussienFahmy 是的,你可以使用@Ignore。但是,您仍然需要 POJO(或 POJO)来提取子项,因此将 POJO 的真实/完整/完整对象和实体视为存储和检索数据的手段可能会更简单。跨度>
    • 在我的用例中,数据来自 firestore 数据库,我需要将其保留为 firestor,在此用例中使用空间来保存离线数据,但使用关系数据代替循环将每个成员分配到其团队,谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-07
    • 2022-07-08
    • 2022-01-06
    • 2018-07-04
    • 1970-01-01
    • 1970-01-01
    • 2015-10-19
    相关资源
    最近更新 更多