【问题标题】:How to model many-to-many relationship in Android Room with CrossRef Junction如何使用 CrossRef Junction 在 Android Room 中建模多对多关系
【发布时间】:2020-02-18 18:09:45
【问题描述】:

我将如何建模https://developer.android.com/training/data-storage/room/relationships#many-to-many 中描述的多对多关系,但在联结点上有一个附加属性?我基本上想实现以下目标:

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val playlistName: String
)

@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)

@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long,
    val rating: Int // <-- the additional property
)

data class PlaylistWithRating(
    val playlist: Playlist,
    val rating: Int // <-- the additional property
)

data class SongWithPlaylists(
    @Embedded val song: Song,
    @Relation(
         parentColumn = "songId",
         entityColumn = "playlistId",
         associateBy = @Junction(PlaylistSongCrossRef::class)
    )
    val playlists: List<PlaylistWithRating>
)

所以我可以在我的 Dao 中访问它:

@Dao
interface SongWithPlaylistsDao {
    @Query("SELECT * FROM Song")
    fun list(): LiveData<List<SongWithPlaylists>>
}

我知道,从 ERM 的角度来看,您将如何为这种关系建模:

/-- A ---\      /- ACrossB -\            /-- B ---\
|        |      |           |            |        |
| - id   |----->|  - aId    |     |------| - id   |
| - name |      |  - bId    |-----|      |  -name |
|        |      |  - prop   |            |        |
\--------/      \-----------/            \--------/

我也知道如何使用 JOIN 查询此关系,但无法从文档中弄清楚如何在 Room 中执行此操作同时保持数据完整性。

【问题讨论】:

  • 我想知道您的附加财产rating 是做什么用的?播放列表还是歌曲?
  • 评分是针对歌曲和播放列表的组合。一首歌在不同的播放列表中可能有不同的评分。
  • 你解决了吗?我有一个类似的问题(我需要额外的属性来排序播放列表中的歌曲)
  • 不,抱歉。我没有时间继续研究 Android Room。
  • @Jeremy 我的英语很差,但我在issuetracker.google.com/issues/169467510 的谷歌问题跟踪器中打开了一个问题我遇到了同样的问题,我认为唯一的解决方法是使用脏 Sqlite

标签: android kotlin android-room


【解决方案1】:

你试过了吗:

@实体 数据类 PlaylistSongCrossRef( val playlistId:长, val songId:长,

 @PrimaryKey(autoGenerate = true)
val rating: Int // <-- the additional property

)

注意:不确定在 kotlin 中声明主节点的方式,我是 java 开发者。 但你有这个想法.. 联结仍然有效,但您可以通过独特的评级区分同一首歌曲的许多关联。

【讨论】:

    【解决方案2】:

    我认为您不需要创建新的数据类PlaylistWithRating。因为您可以直接将附加属性rating 设置为实体。例如,

    如果你的ratingplaylist,你可以如下设置这个属性。

    @Entity
    data class Playlist(
        @PrimaryKey val playlistId: Long,
        val userCreatorId: Long,
        val playlistName: String,
        val rating: Int // <-- the additional property
    )
    

    如果你的ratingsong,你可以如下设置这个属性。

    @Entity
    data class Song(
        @PrimaryKey val songId: Long,
        val songName: String,
        val artist: String,
        val rating: Int // <-- the additional property
    )
    

    因此,您提到的文档中的架构的many-to-many 关系将是正确的。您无需为rating 属性考虑额外的数据类。

    所以,可能如下。

    @Entity
    data class Playlist(
        @PrimaryKey val playlistId: Long,
        val userCreatorId: Long,
        val playlistName: String,
        val rating: Int // <-- the additional property
    )
    
    @Entity
    data class Song(
        @PrimaryKey val songId: Long,
        val songName: String,
        val artist: String
    )
    
    @Entity(primaryKeys = ["playlistId", "songId"])
    data class PlaylistSongCrossRef(
        val playlistId: Long,
        val songId: Long
    )
    
    data class PlaylistWithSongs(
        @Embedded val playlist: Playlist,
        @Relation(
             parentColumn = "playlistId",
             entityColumn = "songId",
             associateBy = @Junction(PlaylistSongCrossRef::class)
        )
        val songs: List<Song>
    )
    
    data class SongWithPlaylists(
        @Embedded val song: Song,
        @Relation(
             parentColumn = "songId",
             entityColumn = "playlistId",
             associateBy = @Junction(PlaylistSongCrossRef::class)
        )
        val playlists: List<Playlist>
    )
    

    我希望这会有所帮助。

    【讨论】:

    • 感谢您的努力。但是让我们假设我们不能将该附加属性(在这种情况下为评级)分配给任何实体。我遇到了一个类似的问题,附加属性纯粹用于关联实体。
    【解决方案3】:

    我遇到了同样的问题,最后以其他方式解决了。在某些情况下,您无法将属性分配给二元关系的任何一方,因此该属性属于关系本身。

    步骤 1 -> 定义实体 A 和实体 B

    @Entity
    data class Playlist(
        @PrimaryKey val playlistId: Long,
        val playlistName: String
    )
    
    @Entity
    data class Song(
        @PrimaryKey val songId: Long,
        val songName: String,
        val artist: String
    )
    

    Step2 -> 定义关联实体(CrossRef 实体),并将您的附加属性添加到它

    @Entity(primaryKeys = ["playlistId", "songId"])
    data class PlaylistSongCrossRef(
        val playlistId: Long,
        val songId: Long,
        val rating: Int // <-- the additional property
    )
    

    到现在为止都是重复你的代码:D

    Step3 -> 定义适合您查询需求的所需数据模型(Pojo)

    让我们考虑你想使用SongWithPlaylists 模型类。在SongWithPlaylists 中,您应该从playlists 中省略@Relation 注释,而是使用@Embedded 对其进行注释。

    注意->playlists 类型从List&lt;PlaylistWithRating&gt; 更改为Playlist。无需使用PlaylistWithRating,只需播放列表,甚至没有它的列表。 Room 不再为我们管理加入。我们会成功的:D 并且还将您的 CrossRef 的附加属性添加到SongWithPlaylists,因为我们希望在连接查询完成后将该附加属性映射到该对象。 SongWithPlayList 会像下面这样

    data class SongWithPlaylists(
        @Embedded val song: Song,
        @Embedded val playlist: Playlist,
        val rating: Int
    )
    

    Step4 -> 将纯 Join 查询添加到您的 DAO

    现在您摆脱了@Relation 注释和PlaylistWithRatings,您应该在实体B 上内部加入您的实体A,然后再次加入CrossRef 实体。
    DAO 就像下面这样

    @Dao
    interface SongWithPlaylistsDao {
        @Query("""
            select Song.songId,Song.songName,Song.artist,Playlist.playlistId,Playlist.playlistName,PlaylistSongCrossRef.rating
            from Song inner join PlaylistSongCrossRef on Song.songId = PlaylistSongCrossRef.songId 
            inner join Playlist on Playlist.playlistId = PlaylistSongCrossRef.playlistId
            """)
        fun list(): LiveData<List<SongWithPlaylists>>
    }
    

    这是肮脏的方式,但对我有用。对不起我的英语不好。我希望对您有所帮助,任何人都可能会遇到同样的问题。

    【讨论】:

      猜你喜欢
      • 2023-01-12
      • 2022-07-08
      • 1970-01-01
      • 1970-01-01
      • 2017-10-19
      • 1970-01-01
      • 1970-01-01
      • 2017-10-20
      • 1970-01-01
      相关资源
      最近更新 更多