【问题标题】:How to insert, update and delete a many-to-many relationship in android room?如何在android房间中插入、更新和删除多对多关系?
【发布时间】:2021-05-07 21:35:25
【问题描述】:

遵循多对多关系的 Android 文档,可在此处找到:Define many-to-many relationships

我有以下实体

@Entity
class Song(
    @PrimaryKey val songId: Long,
    @ColumnInfo val name: String,
    @ColumnInfo val path: Uri,
)
@Entity
data class PlaylistInfo(
    @ColumnInfo var name: String,
    @PrimaryKey(autoGenerate = true) val playlistId: Long = 0,
)

然后我有一个实体,它是两者之间的交叉引用。

@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

最后,我有一个使用PlaylistSongCrossRef 作为连接点将两者结合起来的类。

data class Playlist(
    @Embedded val info: PlaylistInfo,
    @Relation(
        parentColumn = "playlistId",
        entityColumn = "songId",
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: MutableList<Song>
)

然后我在 Dao 中定义了读取查询。

文档到此结束。我现在正试图弄清楚如何插入、更新或删除Playlist

我尝试在 Dao 中定义以Playlist 作为参数并使用@Insert@Update@Delete 注释的函数,但它们给了我以下错误:

错误:参数的类型必须是使用@Entity 注释的类或其集合/数组。 com.user.musicplayer.models.entities.Playlist 播放列表

我不确定从这里去哪里。如果有人可以将我推荐给可以帮助我的资源或提供有效的实现,那将不胜感激。

【问题讨论】:

  • 它部分回答了我的问题。我知道如何使用它来创建新的播放列表,但我想知道如何将歌曲添加到播放列表中。我是否必须创建交叉引用对象并直接添加它们?
  • 是的,很遗憾,我不知道更好的方法。 Room 只是对查询的抽象,它并没有为关系和外键等提供大量便利。

标签: android kotlin android-room android-room-relation


【解决方案1】:

我现在正试图弄清楚如何插入、更新或删除播放列表

播放列表是一个 POJO,旨在(与 Embedded/@Relation 一起使用时)从数据库中提取数据,而不是修改数据库中的数据。也就是说,这是一种方式。

但是,如果它不是数据类而是类(我认为),那么您可以添加函数来执行此类操作。例如

class Playlist {

    @Embedded
    var playlistInfo: PlaylistInfo? = null
    @Relation(
        parentColumn = "playlistId",
        entityColumn = "songId",
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    var songs: MutableList<Song>? = null

    fun deletePlayList(dao: AllDao, playlist: Playlist, deleteOrphanedSongs: Boolean = false) {
        val currentPlaylistInfo: PlaylistInfo? = playlist.playlistInfo
        if (currentPlaylistInfo != null) {
            dao.deleteByPlayListId(currentPlaylistInfo.playlistId)
            dao.delete(currentPlaylistInfo)
            if (deleteOrphanedSongs) {
                dao.deleteManySongs(playlist.songs!!)
            }
        }
    }
}

您可以使用提取的播放列表执行的操作是删除播放列表和歌曲,例如:-

@Delete()
fun deletePlayListInfoAndSongs(playlistInfo: PlaylistInfo, songs: List<Song>): Int
  • 即使用提取的播放列表,您将拥有 Embedded PlaylistInfo 和相关的歌曲

但是,警告,上面的不会删除关联的 PlaylistSongCrossRef 行,因此您最终会得到可能有问题的孤儿。

您可能希望考虑定义外键并使用 onDelete 和/或 onUpdate 操作进行 CASCADE 删除。

示例/演示

以下是基于您的实体的示例(歌曲路径更改为字符串 而不是 URI 方便)和上面的 Playlist 类。

供您考虑的道是:-

@Dao
interface AllDao {
    @Insert
    fun insert(playlistInfo: PlaylistInfo): Long
    @Insert
    fun insertManyPlaylistInfos(playlistInfoList: List<PlaylistInfo>): LongArray
    @Insert
    fun insert(song: Song): Long
    @Insert
    fun insertManySongs(songList: List<Song>): LongArray
    @Insert
    fun insert(playlistSongCrossRef: PlaylistSongCrossRef): Long
    @Insert
    fun insertManyPlaylistSongCrossrefs(playlistSongCrossRefList: List<PlaylistSongCrossRef>): LongArray

    @Query("SELECT * FROM song")
    fun getAllSongs(): List<Song>
    @Query("SELECT * FROM song WHERE song.name = :songname LIMIT 1")
    fun getFirstSongByName(songname: String): Song
    @Query("SELECT * FROM playlistInfo")
    fun getAllPlaylistInfos(): List<PlaylistInfo>
    @Query("SELECT * FROM playlistinfo WHERE playlistinfo.name = :playlistname LIMIT 1")
    fun getFirstPlaylistInfoByName(playlistname: String): PlaylistInfo
    @Query("SELECT * FROM playlistsongcrossref")
    fun getAllPlaylistSongCrossRefs(): List<PlaylistSongCrossRef>
    @Transaction
    @Query("SELECT * FROM playlistinfo")
    fun getAllPlaylists(): List<Playlist>

    @Query("SELECT count(*) FROM song")
    fun getNumberOfSongs(): Long
    @Query("SELECT count(*) FROM playlistinfo")
    fun getNumberOfPlaylistInfos(): Long
    @Query("SELECT count(*) FROM playlistsongcrossref")
    fun getNumberOfSongsInPlaylists(): Long
    @Update
    fun update(song: Song)
    @Update
    fun update(playlistInfo: PlaylistInfo)

    /* Suggested NEVER to use */
    @Update
    fun update(playlistSongCrossRef: PlaylistSongCrossRef)

    @Delete
    fun delete(playlistInfo: PlaylistInfo): Int
    @Delete()
    fun delete(song: Song): Int
    @Delete
    fun deleteManySongs(songs: List<Song>): Int
    @Delete
    fun delete(playlistSongCrossRef: PlaylistSongCrossRef): Int
    @Delete()
    fun deletePlayListInfoAndSongs(playlistInfo: PlaylistInfo, songs: List<Song>): Int
    //@Delete
    //fun deletePlayListInfoAndSongs(playlist: Playlist): Int
    @Query("DELETE FROM playlistsongcrossref WHERE playlistId = :playlistId")
    fun deleteByPlayListId(playlistId: Long): Int
    @Query("DELETE FROM playlistsongcrossref WHERE playlistId = :playlistId AND songId = :songId")
    fun deleteSongFromPlaylist(playlistId: Long, songId: Long)
}

也许在一个显示更新、删除和插入示例的 Activity 中考虑以下内容:-

class MainActivity : AppCompatActivity() {

    private val TAG = "PLDBINFO"
    lateinit var db: TheDatabase
    lateinit var dao: AllDao
    val songList = listOf<Song>(
        Song(1L,"My Song","x"),
        Song(2L,"Your Song","Y"),
        Song(3L,"Her Song","Z"),
        Song(4L,"His Song","A"),
        Song(5L,"Their Song","B"),
        Song(6L,"Nobody's Song","C")
    )
    val playlistInfoList = listOf<PlaylistInfo>(
        PlaylistInfo("My PlayList"),
        PlaylistInfo("Your PlayList"),
        PlaylistInfo("Her PlayList")
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Get the Database and the Dao
        db = Room.databaseBuilder(this,TheDatabase::class.java,"playlist.db")
            .allowMainThreadQueries()
            .build()
        dao = db.getDao()

        // Add all the Playlist and Songs and add every Song to all the Playlists
        // ONLY IF no data exists
        if (dao.getNumberOfPlaylistInfos() + dao.getNumberOfPlaylistInfos() + dao.getNumberOfSongsInPlaylists() < 1) {
            dao.insertManySongs(songList)
            dao.insertManyPlaylistInfos(playlistInfoList)
            for(p: PlaylistInfo in dao.getAllPlaylistInfos()) {
                for(s: Song in dao.getAllSongs()) {
                    dao.insert(PlaylistSongCrossRef(p.playlistId,s.songId))
                }
            }
        }

        // Write the counts to the Log
        logStats()

        // Extract All Core Objects
        var allPlaylistInfos = dao.getAllPlaylists()
        var allSongs = dao.getAllSongs()
        var allPlaylists = dao.getAllPlaylists()
        var allPlaylistSongCrossRefs = dao.getAllPlaylistSongCrossRefs()
        Log.d(TAG,"Extracted PlaylistInfos = ${allPlaylistInfos.size} " +
                "Extacted Songs = ${allSongs.size} " +
                "Extracted Playlists = ${allPlaylists.size} " +
                "Extracted PlaylistSongCrossRefs = ${allPlaylistSongCrossRefs.size}"
        )


        // Add a new song and also put it into the My PlayList playlist
        dao.insert(PlaylistSongCrossRef(dao.getFirstPlaylistInfoByName("My PlayList").playlistId,dao.insert(Song(10L,"Bert's Song","D"))))
        // ASame as above BUT using function getPlaylistInfoIdByName below that gets the ID
        dao.insert(PlaylistSongCrossRef(getPlaylistInfoIdByName("My PlayList"),dao.insert(Song(20L,"Mary's Song","E"))))

        //
        var otherPlTodelete = dao.getFirstPlaylistInfoByName("Her PlayList")
        for(pl: Playlist in allPlaylistInfos) {
            /* Best to not use this
            if (pl.playlistInfo!!.playlistId == plToDelete.playlistId ) {
                dao.deletePlayListInfoAndSongs(pl.playlistInfo!!,pl.songs!!)
            }

             */
            if (pl.playlistInfo!!.playlistId == otherPlTodelete.playlistId) {
                pl.deletePlayList(dao,pl,false) /* best to only use false */
            }
        }

        var playlistToUpdate = dao.getFirstPlaylistInfoByName("Your PlayList")
        playlistToUpdate.name = "PlayList that is for Your but it was Your PlayList"
        dao.update(playlistToUpdate)

        logStats()
    }

    private fun logStats() {
        Log.d(TAG," Playlist Count = " + dao.getNumberOfPlaylistInfos()
                + " Song Count = " + dao.getNumberOfSongs()
                + " Song/Playlist count = " + dao.getNumberOfSongsInPlaylists()
        )
    }

    private fun getSongIdByName(songName: String): Long {
        return dao.getFirstSongByName(songName).songId
    }
    private fun getPlaylistInfoIdByName(playlistInfoName: String): Long {
        return dao.getFirstPlaylistInfoByName(playlistInfoName).playlistId
    }
}

首先,如果不存在数据(通过查询获取行数的示例),则添加 3 个播放列表和 6 首歌曲,每个播放列表包含所有歌曲(因此 18 个交叉引用行)。

  • 显然这不是真实的情况(当然,它非常方便/易于测试)

然后是统计信息(每个表的行输出到日志)。

然后提取所有类型(PlaylistInfo's、Song's、PlaylistSongCrossref's 和 PlayList's)并显示计数(应与统计数据相符)。

然后两首新歌曲不仅作为歌曲添加,而且还添加到名为“我的播放列表”的播放列表中

根据名称找到名为“Her PlayList”的播放列表信息(又名播放列表)。它与 PlayListSongCrossRef 一起被删除,因此不会通过 Playlists 类中的函数留下孤立的歌曲。

然后,名为“Your PlayList”的播放列表被命名为“PlayList that is for Your but it was Your PlayList”。

【讨论】:

    猜你喜欢
    • 2012-03-23
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 2019-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多