Comment puis-je représenter plusieurs à plusieurs relations avec Room? par exemple. J'ai "Invité" et "Réservation". La réservation peut avoir de nombreux invités et un invité peut faire partie de nombreuses réservations.
Voici mes définitions d'entité:
@Entity data class Reservation(
@PrimaryKey val id: Long,
val table: String,
val guests: List<Guest>
)
@Entity data class Guest(
@PrimaryKey val id: Long,
val name: String,
val email: String
)
En cherchant dans les documents je suis tombé sur @Relation
. J'ai trouvé cela très déroutant cependant.
Selon cela, je voudrais créer un POJO et y ajouter les relations. Donc, avec mon exemple, j'ai fait ce qui suit
data class ReservationForGuest(
@Embedded val reservation: Reservation,
@Relation(
parentColumn = "reservation.id",
entityColumn = "id",
entity = Guest::class
) val guestList: List<Guest>
)
Avec ce qui précède, j'obtiens l'erreur du compilateur:
Impossible de comprendre comment lire ce champ à partir d'un curseur.
Je n'ai pas pu trouver d'échantillon de travail de @Relation
.
J'ai eu un problème similaire. Voici ma solution.
Vous pouvez utiliser une entité supplémentaire (ReservationGuest
) qui conserve la relation entre Guest
et Reservation
.
@Entity data class Guest(
@PrimaryKey val id: Long,
val name: String,
val email: String
)
@Entity data class Reservation(
@PrimaryKey val id: Long,
val table: String
)
@Entity data class ReservationGuest(
@PrimaryKey(autoGenerate = true) val id: Long,
val reservationId: Long,
val guestId: Long
)
Vous pouvez obtenir des réservations avec leur liste de guestId
s. (Pas les objets invités)
data class ReservationWithGuests(
@Embedded val reservation:Reservation,
@Relation(
parentColumn = "id",
entityColumn = "reservationId",
entity = ReservationGuest::class,
projection = "guestId"
) val guestIdList: List<Long>
)
Vous pouvez également obtenir des invités avec leur liste de reservationId
s. (Pas les objets de réservation)
data class GuestWithReservations(
@Embedded val guest:Guest,
@Relation(
parentColumn = "id",
entityColumn = "guestId",
entity = ReservationGuest::class,
projection = "reservationId"
) val reservationIdList: List<Long>
)
Puisque vous pouvez obtenir les guestId
s et reservationId
s, vous pouvez interroger les entités Reservation
et Guest
avec celles-ci.
Je mettrai à jour ma réponse si je trouve un moyen facile d'extraire la liste des objets de réservation et d'invité au lieu de leurs identifiants.
En fait, il y a une possibilité supplémentaire d'obtenir la liste Guest
, pas seulement les identifiants comme dans @ Devrim answer.
Commencez par définir la classe qui représentera la connexion entre Guest
et Reservation
.
@Entity(primaryKeys = ["reservationId", "guestId"],
foreignKeys = [
ForeignKey(entity = Reservation::class,
parentColumns = ["id"],
childColumns = ["reservationId"]),
ForeignKey(entity = Guest::class,
parentColumns = ["id"],
childColumns = ["guestId"])
])
data class ReservationGuestJoin(
val reservationId: Long,
val guestId: Long
)
Chaque fois que vous insérerez un nouvel objet Reservation
, vous devrez insérer un objet ReservationGuestJoin
afin de respecter la contrainte de clé étrangère. Et maintenant, si vous voulez obtenir la liste Guest
, vous pouvez utiliser la puissance de la requête SQL:
@Dao
interface ReservationGuestJoinDao {
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
@Query("""
SELECT * FROM guest INNER JOIN reservationGuestJoin ON
guest.id = reservationGuestJoin.guestId WHERE
reservationGuestJoin.reservationId = :reservationId
""")
fun getGuestsWithReservationId(reservationId: Long): List<Guest>
}
Pour plus de détails, visitez ce blog .
Voici un moyen d'interroger un modèle d'objet complet via une table de jonctions M: N en une seule requête. Les sous-requêtes ne sont probablement pas le moyen le plus efficace de le faire, mais cela fonctionne jusqu'à ce qu'elles aient @Relation
pour parcourir correctement ForeignKey
. J'ai jeté à la main le cadre Invité/Réservation dans mon code de travail afin qu'il puisse contenir des fautes de frappe.
Entity (Cela a été couvert)
@Entity data class Guest(
@PrimaryKey val id: Long,
val name: String,
val email: String
)
@Entity data class Reservation(
@PrimaryKey val id: Long,
val table: String
)
@Entity data class ReservationGuest(
@PrimaryKey(autoGenerate = true) val id: Long,
val reservationId: Long,
val guestId: Long
)
Dao (Notez que nous extrayons le M: N via une sous-requête et réduisons les lignes supplémentaires Reservation
avec un GROUP_CONCAT
@Query("SELECT *, " +
"(SELECT GROUP_CONCAT(table) " +
"FROM ReservationGuest " +
"JOIN Reservation " +
"ON Reservation.id = ReservationGuest.reservationId " +
"WHERE ReservationGuest.guestId = Guest.id) AS tables, " +
"FROM guest")
abstract LiveData<List<GuestResult>> getGuests();
GuestResult (Ceci gère le mappage du résultat de la requête. Notez que nous convertissons la chaîne concaténée en une liste avec @TypeConverter
)
@TypeConverters({ReservationResult.class})
public class GuestResult extends Guest {
public List<String> tables;
@TypeConverter
public List<String> fromGroupConcat(String reservations) {
return Arrays.asList(reservations.split(","));
}
}
Pour l'entité de table de jointure, je suggère d'utiliser un ID composite indexé:
@Entity(
primaryKeys = ["reservationId", "guestId"],
indices = [Index(value =["reservationId", "guestId"], unique = true)]
)
data class ReservationGuestJoin(
@PrimaryKey(autoGenerate = true) var id: Long,
var reservationId: Long = 0,
var guestId: Long = 0
)
Le GuestDao.kt:
@Dao
@TypeConverters(GuestDao.Converters::class)
interface GuestDao {
@Query(QUERY_STRING)
fun listWithReservations(): LiveData<List<GuestWithReservations>>
data class GuestWithReservations(
var id: Long? = null,
var name: String? = null,
var email: String? = null,
var reservations: List<Reservation> = emptyList()
)
class Converters{
@TypeConverter
fun listReservationFromConcatString(value: String?): List<Reservation>? = value?.let { value ->
.split("^^")
.map { it.split("^_") }
.map { Reservation(id = it.getOrNull(0)?.toLongOrNull(), name = it.getOrNull(1)) }
} ?: emptyList()
}
}
Le QUERY_STRING
. Nous faisons des jointures internes pour produire une grande table avec les données des deux entités, nous concaténons les données de Reservation
sous forme de chaîne de colonne et nous groupons enfin les lignes avec l'ID invité, en concaténant les chaînes de réservation avec des séparateurs différents. , notre convertisseur se chargera de le reconstruire en tant qu’entité:
SELECT
t.id, t.name, t.email, GROUP_CONCAT(t.reservation, '^^') as reservations
FROM (
SELECT
guestId as id, name, email, (reservationId || '^_' || reservationTable) as reservation
FROM
GuestReservationJoin
INNER JOIN Guest ON Guest.id = GuestReservationJoin.guestId
INNER JOIN Reservation ON Reservation.id = GuestReservationJoin.reservationId
) as t
GROUP BY t.id
Notez que j'ai changé votre colonne table
nom car je pense que Room ne vous permet pas d'utiliser des noms réservés SQLite.
Je n'ai pas testé les performances de tout cela par rapport à avoir une entité plus plate (une autre option sans les concaténations). Si je le fais, je mettrai à jour ma réponse.