Comment puis-je représenter un grand nombre de relations avec Android Room?
Comment puis-je représenter une relation de plusieurs à plusieurs avec la chambre? par exemple, j'ai "Invité" et "Réservation". Réservation peut avoir de nombreux invités et un invité peut faire partie de nombreuses réservations.
Voici les définitions de mon 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 regardant dans docs je suis tombé sur @Relation
. J'ai trouvé ça vraiment bien que déroutante.
selon ceci je voudrais créer un POJO et ajouter le les relations de là. Ainsi, 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 Ci-dessus je reçois l'erreur du compilateur:
Ne peut pas comprendre comment lire ce domaine à partir d'un curseur.
Je n'ai pas trouvé d'échantillon de travail de @Relation
.
5 réponses
j'ai eu un problème similaire. Voici ma solution.
vous pouvez utiliser une entité supplémentaire ( ReservationGuest
) qui garde 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
. (Pas l'invité d'objets)
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. (Non les objets de la réserve)
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 Reservation
et Guest
entités avec ceux.
je mettrai à jour ma réponse si je trouve un moyen facile d'obtenir la réservation et la liste des objets invités au lieu de leurs identifiants.
Voici une façon d'interroger un modèle d'objet complet à travers une table de jonction M:N dans une seule requête. Les sous-séries ne sont probablement pas la façon la plus efficace de le faire, mais cela fonctionne jusqu'à ce qu'ils obtiennent @Relation
pour marcher correctement à travers ForeignKey
. j'ai mis le cadre Guest/Reservation dans mon code de travail pour qu'il y ait des fautes de frappe.
Entité (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 "
@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 la cartographie du résultat de la requête, notez que nous convertissons la chaîne concaténée retour à 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(","));
}
}
en fait il y a une autre possibilité d'obtenir la liste Guest
, pas seulement des id comme dans @Devrim réponse.
définit D'abord la classe qui représentera le lien 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 le nouveau Reservation
, vous devrez insérer l'objet ReservationGuestJoin
afin de répondre à la contrainte de clé étrangère.
Et maintenant, si vous voulez obtenir Guest
liste vous pouvez utiliser la puissance de 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 .
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 GuestWithReservation(
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 une jonction interne pour produire une grande table avec des données des deux entités, nous concaténons les données de Reservation
comme une chaîne de colonne et enfin nous group_concattons les lignes par l'ID invité, concaténant les chaînes de réservation avec différents séparateurs, notre convertisseur prendra soin 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 nom de colonne table
parce que je pense que Room ne vous permet pas D'utiliser des noms réservés SQLite.
Je n'ai pas testé la performance de tout cela par rapport à avoir entité plus plate (une autre option sans les concaténations). Si je le fais, je mettrai à jour ma réponse.
basé sur la réponse ci-dessus: https://stackoverflow.com/a/44428451/4992598 seulement en gardant des noms de champ séparés entre les entités vous pouvez faire revenir les Modèles (pas seulement les identifiants). Tout ce que vous devez faire est:
@Entity data class ReservationGuest(
@PrimaryKey(autoGenerate = true) val id: Long,
val reservationId: Long,
@Embedded
val guest: Guest
)
et oui les entités peuvent être imbriquées les unes dans les autres à condition que vous ne conserviez pas de champs en double. En conséquence, la classe ReservationWithGuests peut ressembler à ceci.
data class ReservationWithGuests(
@Embedded val reservation:Reservation,
@Relation(
parentColumn = "id",
entityColumn = "reservationId",
entity = ReservationGuest::class,
projection = "guestId"
) val guestList: List<Guest>
)
donc à ce point vous pouvez utiliser val guestIdList: List parce que votre ReservationGuest entity établit en fait des cartes d'identité avec des modèles d'entity.