ルームとの多対多の関係をどのように表現できますか?例えば「ゲスト」と「予約」があります。予約には多くのゲストを含めることができ、ゲストは多くの予約の一部にすることができます。
エンティティの定義は次のとおりです。
@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
の実際のサンプルを見つけることができませんでした。
同様の問題がありました。これが私の解決策です。
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
sおよびreservationId
sを取得できるため、それらを使用してReservation
およびGuest
エンティティをクエリできます。
IDの代わりに予約およびゲストオブジェクトリストを取得する簡単な方法を見つけた場合、回答を更新します。
実際には、 @ Devrim answerのようなidだけでなく、Guest
リストを取得する可能性がもう1つあります。
最初に、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>
}
詳細については、 このブログ をご覧ください。
1つのクエリでM:Nジャンクションテーブルを介して完全なオブジェクトモデルをクエリする方法を次に示します。サブクエリはおそらくこれを行う最も効率的な方法ではありませんが、@Relation
を取得してForeignKey
を適切に処理するまで機能します。 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
)
Dao(サブクエリ経由でM:Nを取得し、GROUP_CONCAT
で余分なReservation
行を減らすことに注意してください
@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(","));
}
}
結合テーブルエンティティには、インデックス付きの複合IDを使用することをお勧めします。
@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
)
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
からのデータを列文字列として連結し、最後にゲストIDによって行をgroup_concatし、予約文字列を異なるセパレータで連結します。コンバータはエンティティとして再構築します:
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
ルームではSQLiteの予約名を使用できないと思うため、列table
の名前を変更したことに注意してください。
このすべてのパフォーマンスを、よりフラットなエンティティ(連結なしの別のオプション)と比較してテストしませんでした。もしそうなら、答えを更新します。