web-dev-qa-db-fra.com

Comment puis-je représenter plusieurs relations avec Android Room?

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.

42
bond

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 guestIds. (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 reservationIds. (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 guestIds et reservationIds, 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.

réponse similaire

69
Devrim

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 .

8
Nominalista

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(","));
    }
}
7
Anthony

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 tablenom 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.

0
Allan Veloso