tutorial room example ejemplo android kotlin android-room android-architecture-components

android - ejemplo - kotlin room example



¿Cómo puedo representar una relación de muchos a muchos con Android Room? (5)

Aquí hay una forma de consultar un modelo de objeto completo a través de una tabla de unión M: N en una sola consulta. Las subconsultas probablemente no sean la forma más eficiente de hacer esto, pero funciona hasta que logran que @Relation camine adecuadamente a través de ForeignKey . Metí a mano el marco Invitado / Reservación en mi código de trabajo para que pudiera haber errores tipográficos.

Entidad (esto ha sido cubierto)

@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 (Tenga en cuenta que colocamos M: N a través de una subconsulta y reducimos las filas de Reservation adicionales con 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 (Esto controla la asignación del resultado de la consulta, tenga en cuenta que convertimos la cadena concatenada de nuevo a una lista con @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(",")); } }

¿Cómo puedo representar una relación de muchos a muchos con Room? Por ejemplo, tengo "Invitado" y "Reservación". La reservación puede tener muchos Invitados y un Invitado puede ser parte de muchas Reservaciones.

Aquí están mis definiciones de entidad:

@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 )

Mientras miraba los documentos me encontré con @Relation . Aunque lo encontré muy confuso.

De acuerdo con esto, me gustaría crear un POJO y agregar las relaciones allí. Entonces, con mi ejemplo hice lo siguiente.

data class ReservationForGuest( @Embedded val reservation: Reservation, @Relation( parentColumn = "reservation.id", entityColumn = "id", entity = Guest::class ) val guestList: List<Guest> )

Con lo anterior obtengo el error del compilador:

No se puede averiguar cómo leer este campo desde un cursor.

No pude encontrar una muestra de trabajo de @Relation .


Basándose en la respuesta anterior: https://.com/a/44428451/4992598 solo manteniendo nombres de campo separados entre entidades, puede recibir modelos devueltos (no solo identificadores). Todo lo que necesitas hacer es:

@Entity data class ReservationGuest( @PrimaryKey(autoGenerate = true) val id: Long, val reservationId: Long, @Embedded val guest: Guest )

Y sí, las entidades se pueden incrustar entre sí siempre y cuando no se mantengan campos duplicados. Entonces, en consecuencia, la clase ReservationWithGuests puede verse así.

data class ReservationWithGuests( @Embedded val reservation:Reservation, @Relation( parentColumn = "id", entityColumn = "reservationId", entity = ReservationGuest::class, projection = "guestId" ) val guestList: List<Guest> )

Entonces, en este punto, puede usar val guestIdList: List porque su entidad ReservationGuest en realidad mapea los identificadores con los modelos de entidad.


En realidad, hay una posibilidad más para obtener la lista de Guest , no solo los id como en la respuesta de @Devrim .

Primero defina la clase que representará la conexión entre el Guest y la 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 )

Cada vez que inserte una nueva Reservation , deberá insertar el objeto ReservationGuestJoin para cumplir con la restricción de clave externa. Y ahora, si desea obtener la lista de Guest , puede usar el poder de la consulta 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> }

Para ver más detalles visita este blog .


Para la entidad de la tabla de unión, sugiero usar un ID compuesto indexado:

@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 )

El 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() } }

El QUERY_STRING . Hacemos uniones internas para producir una gran tabla con datos de ambas entidades, las concatenamos como una cadena de columnas y por último agrupamos las filas por el ID del huésped, concatenando las cadenas de reserva con diferentes separadores, nuestro convertidor tomará Cuidar de reconstruirlo como una entidad:

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

Tenga en cuenta que cambié el nombre de la table columnas porque creo que Room no le permite usar nombres reservados de SQLite.

No probé el rendimiento de todo esto en comparación con tener una entidad más plana (otra opción sin las concatenaciones). Si lo hago, actualizaré mi respuesta.


Tuve un problema similar Aquí está mi solución.

Puede usar una entidad adicional ( ReservationGuest ) que mantiene la relación entre Guest y 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 )

Puede obtener reservas con su lista de guestId s. (No los objetos invitados)

data class ReservationWithGuests( @Embedded val reservation:Reservation, @Relation( parentColumn = "id", entityColumn = "reservationId", entity = ReservationGuest::class, projection = "guestId" ) val guestIdList: List<Long> )

También puede obtener invitados con su lista de reservationId . (No los objetos de reserva)

data class GuestWithReservations( @Embedded val guest:Guest, @Relation( parentColumn = "id", entityColumn = "guestId", entity = ReservationGuest::class, projection = "reservationId" ) val reservationIdList: List<Long> )

Dado que puede obtener el guestId s y reservationId s, puede consultar las entidades Reservation y Guest con ellos.

Actualizaré mi respuesta si encuentro una forma fácil de buscar la lista de objetos de Reservación e Invitado en lugar de sus ID.

Respuesta similar