m:n-Beziehungen definieren und abfragen

Bei einer m:n-Beziehung zwischen zwei Entitäten entspricht jede Instanz der übergeordneten Entität null oder mehr Instanzen der untergeordneten Entität und umgekehrt.

Betrachten wir als Beispiel die Songs in benutzerdefinierten Playlists in einer Musikstreaming-App. Jede Playlist kann viele Songs enthalten und jeder Song kann zu vielen Playlists gehören. Daher besteht eine m:n-Beziehung zwischen den Entitäten Playlist und Song.

So definieren und fragen Sie m:n-Beziehungen in Ihrer Datenbank ab:

  1. Beziehung definieren: Legen Sie die Entitäten und die assoziative Entität oder die Querverweistabelle fest, um die m:n Beziehung darzustellen.
  2. Entitäten abfragen: Legen Sie fest, wie Sie die verknüpften Entitäten abfragen möchten, und erstellen Sie Datenklassen, um die gewünschte Ausgabe darzustellen.

Beziehung definieren

Um eine m:n-Beziehung zu definieren, erstellen Sie zuerst eine Klasse für jede Ihrer beiden Entitäten. M:n-Beziehungen unterscheiden sich von anderen Beziehungstypen, da es in der untergeordneten Entität in der Regel keinen Verweis auf die übergeordnete Entität gibt. Erstellen Sie stattdessen eine dritte Klasse, um eine assoziative Entität oder Querverweistabelle zwischen den beiden Entitäten darzustellen. Die Querverweistabelle muss Spalten für den Primärschlüssel aus jeder Entität in der m:n-Beziehung haben, die in der Tabelle dargestellt wird. In diesem Beispiel entspricht jede Zeile in der Querverweistabelle einer Paarung einer Playlist-Instanz und einer Song-Instanz, wobei die referenzierte Playlist den referenzierten Song enthält.

@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"], indices = [Index("playlistId", "songId")])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

Entitäten abfragen

Der nächste Schritt hängt davon ab, wie Sie diese verknüpften Entitäten abfragen möchten.

  • Wenn Sie Playlists und eine Liste der entsprechenden Songs für jede Playlist abfragen möchten, erstellen Sie eine neue Datenklasse, die ein einzelnes Playlist-Objekt und eine Liste der Song-Objekte enthält, die in der Playlist enthalten sind.
  • Wenn Sie Songs und eine Liste der entsprechenden Playlists für jeden Song abfragen möchten, erstellen Sie eine neue Datenklasse, die ein einzelnes Song-Objekt und eine Liste der Playlist-Objekte enthält, die den Song enthalten.

Modellieren Sie in beiden Fällen die Beziehung zwischen den Entitäten mit der associateBy Property in der @Relation Annotation in jeder dieser Klassen, um die Querverweisentität zu identifizieren, die die Beziehung zwischen der Playlist Entität und der Song Entität bereitstellt.

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
        parentColumns = ["playlistId"],
        entityColumns = ["songId"],
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)

data class SongWithPlaylists(
    @Embedded val song: Song,
    @Relation(
        parentColumns = ["songId"],
        entityColumns = ["playlistId"],
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val playlists: List<Playlist>
)

Fügen Sie schließlich der Klasse des Datenzugriffsobjekts (Data Access Object, DAO) eine Funktion hinzu, um die Abfragefunktion verfügbar zu machen, die Ihre App benötigt.

getPlaylistsWithSongs
Fragt die Datenbank ab und gibt alle resultierenden PlaylistWithSongs-Objekte zurück.
getSongsWithPlaylists
Fragt die Datenbank ab und gibt alle resultierenden SongWithPlaylists-Objekte zurück.

Für jede Funktion muss Room zwei Abfragen ausführen. Fügen Sie beiden Funktionen die @Transaction Annotation hinzu, um sicherzustellen, dass der Vorgang atomar ausgeführt wird.

@Transaction
@Query("SELECT * FROM Playlist")
suspend fun getPlaylistsWithSongs(): List<PlaylistWithSongs>

@Transaction
@Query("SELECT * FROM Song")
suspend fun getSongsWithPlaylists(): List<SongWithPlaylists>

Zusammengesetzte Schlüssel

Wenn Sie die Beziehung mit zusammengesetzten Schlüsseln definieren, geben Sie in parentColumns und entityColumns der Annotation @Relation mehrere Spalten an.

Wenn Sie Spalten in der Junction angeben müssen, verwenden Sie auch parentColumns und entityColumns in der Annotation Junction.

Im folgenden Beispiel hat Playlist einen zusammengesetzten Primärschlüssel, der aus playlistId und creatorId besteht. Die Querverweistabelle PlaylistSongCrossRef enthält auch diese Spalten, um auf die Playlist zu verweisen.

@Entity(primaryKeys = ["playlistId", "creatorId"])
data class Playlist(
    val playlistId: Long,
    val creatorId: Long,
    val playlistName: String
)

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

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

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
        parentColumns = ["playlistId", "creatorId"],
        entityColumns = ["songId"],
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)