Как я могу представить отношение многих к многим с Android Room?
Как я могу представить отношение многих к многим с комнатой?
например У меня есть "Гость" и "Бронирование". В резервировании может быть много гостей, а гость может быть частью многих резерваций.
Вот мои определения сущностей:
@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
)
При просмотре документов я наткнулся на @Relation
. Я нашел это действительно запутанным, хотя.
В соответствии с этим я хотел бы создать POJO и добавить туда отношения. Итак, в моем примере я сделал следующее
data class ReservationForGuest(
@Embedded val reservation: Reservation,
@Relation(
parentColumn = "reservation.id",
entityColumn = "id",
entity = Guest::class
) val guestList: List<Guest>
)
Свыше я получаю ошибку компилятора:
Невозможно понять, как читать это поле из курсора.
Мне не удалось найти рабочий образец @Relation
.
Ответы
Ответ 1
У меня была аналогичная проблема. Вот мое решение.
Вы можете использовать дополнительную сущность (ReservationGuest
), которая сохраняет связь между Guest
и 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
)
Вы можете получить резервирование со своим списком guestId
s. (Не гостевые объекты)
data class ReservationWithGuests(
@Embedded val reservation:Reservation,
@Relation(
parentColumn = "id",
entityColumn = "reservationId",
entity = ReservationGuest::class,
projection = "guestId"
) val guestIdList: List<Long>
)
Вы также можете получить гостей со списком reservationId
s. (Не объекты резервирования)
data class GuestWithReservations(
@Embedded val guest:Guest,
@Relation(
parentColumn = "id",
entityColumn = "guestId",
entity = ReservationGuest::class,
projection = "reservationId"
) val reservationIdList: List<Long>
)
Поскольку вы можете получить guestId
и reservationId
s, вы можете запросить Reservation
и Guest
сущности с ними.
Я обновлю свой ответ, если найду простой способ получить список объектов Reservation и Guest вместо своих идентификаторов.
Подобный ответ
Ответ 2
На самом деле есть еще одна возможность получить список Guest
, а не только идентификатор, как в @Devrim ответе.
Сначала определите класс, который будет представлять связь между Guest
и 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
)
Каждый раз, когда вы будете вставлять новый Reservation
, вам нужно будет вставить объект ReservationGuestJoin
, чтобы выполнить ограничение внешнего ключа.
И теперь, если вы хотите получить список Guest
, вы можете использовать мощность 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>
}
Чтобы узнать больше, посетите этот блог.
Ответ 3
Вот способ запроса полной объектной модели через таблицу соединений M: N в одном запросе. Подзапросы, вероятно, не самый эффективный способ сделать это, но он работает, пока они не получат @Relation
, чтобы правильно пройти через ForeignKey
. Я вручную закрепил рамки Guest/Reservation в своем рабочем коде, чтобы могли быть опечатки.
Entity (это было рассмотрено)
@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
)
Дао (обратите внимание, что мы втягиваем M: N через подзапрос и уменьшаем лишние строки Reservation
с 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 (Это обрабатывает отображение результата запроса, обратите внимание, что мы преобразуем конкатенированную строку обратно в список с помощью @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(","));
}
}
Ответ 4
Для объекта объединяющей таблицы я предлагаю использовать составной индексированный индекс:
@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
)
The 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()
}
}
QUERY_STRING
. Мы создаем внутренние объединения, чтобы создать большую таблицу с данными обеих сущностей, затем мы объединяем данные из Reservation
в виде строки столбца и, наконец, группируем строки по идентификатору гостя, объединяя строки резервирования с различными разделителями, наш конвертер позаботится о том, чтобы перестроить его как единое целое:
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
Обратите внимание, что я изменил имя вашего столбца table
, потому что я думаю, что Room не позволяет вам использовать зарезервированные имена SQLite.
Я не тестировал производительность всего этого по сравнению с более плоской сущностью (другой вариант без конкатенаций). Если я это сделаю, я обновлю свой ответ.
Ответ 5
С введением Junction в комнату вы можете легко справиться с отношениями "многие ко многим".
Как было сказано @Devrim, вы можете использовать дополнительную сущность (ReservationGuest), которая сохраняет связь между Гостем и Резервированием (также известную как ассоциативная таблица или соединительная таблица или соединительная таблица).
@Entity
data class Guest(
@PrimaryKey
val gId: Long,
val name: String,
val email: String
)
@Entity
data class Reservation(
@PrimaryKey
val rId: Long,
val table: String
)
@Entity(
primaryKeys = ["reservationId", "guestId"]
)
data class ReservationGuest(
val reservationId: Long,
val guestId: Long
)
Теперь вы можете забронировать номер с помощью этой модели:
data class ReservationWithGuests (
@Embedded
val reservation: Reservation,
@Relation(
parentColumn = "rId",
entity = Guest::class,
entityColumn = "gId",
associateBy = Junction(
value = ReservationGuest::class,
parentColumn = "reservationId",
entityColumn = "guestId"
)
)
val guests: List<Guest>
)
Вы также можете получить гостя с их списком бронирования как.
data class GuestWithReservations (
@Embedded
val guest: Guest,
@Relation(
parentColumn = "gId",
entity = Reservation::class,
entityColumn = "rId",
associateBy = Junction(
value = ReservationGuest::class,
parentColumn = "guestId",
entityColumn = "reservationId"
)
)
val reservations: List<Reservation>
)
Теперь вы можете запросить у базы данных результат:
@Dao
interface GuestReservationDao {
@Query("SELECT * FROM Reservation")
fun getReservationWithGuests(): LiveData<List<ReservationWithGuests>>
@Query("SELECT * FROM Guest")
fun getGuestWithReservations(): LiveData<List<GuestWithReservations>>
}
Ответ 6
Исходя из вышеприведенного ответа: fooobar.com/info/296288/..., только сохраняя отдельные имена полей между объектами
вы можете вернуть модели (а не только идентификаторы). Все, что вам нужно сделать, это:
@Entity data class ReservationGuest(
@PrimaryKey(autoGenerate = true) val id: Long,
val reservationId: Long,
@Embedded
val guest: Guest
)
И да объекты могут быть встроены друг в друга, если вы не сохраняете повторяющиеся поля. Таким образом, класс ReservationWithGuests может выглядеть следующим образом.
data class ReservationWithGuests(
@Embedded val reservation:Reservation,
@Relation(
parentColumn = "id",
entityColumn = "reservationId",
entity = ReservationGuest::class,
projection = "guestId"
) val guestList: List<Guest>
)
Итак, на этом этапе вы можете использовать val guestIdList: List, потому что ваш объект ReservationGuest фактически отображает идентификаторы с моделями сущностей.