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 .

26
demandé sur Willi Mentzel 2017-06-05 07:47:42

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.

réponse similaire

47
répondu Devrim 2017-06-26 13:12:03

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(","));
    }
}
5
répondu Anthony 2017-12-31 22:49:32

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 .

1
répondu Nominalista 2018-05-27 09:31:31

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.

0
répondu Magritte 2018-08-24 15:00:35

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.

-1
répondu andu 2017-11-15 16:18:17