web-dev-qa-db-ja.com

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の実際のサンプルを見つけることができませんでした。

42
bond

同様の問題がありました。これが私の解決策です。

ReservationGuestGuestの間の関係を保持する追加のエンティティ(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
)

guestIdsのリストで予約を取得できます。 (ゲストオブジェクトではありません)

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

reservationIdsのリストを使用してゲストを取得することもできます。 (予約オブジェクトではありません)

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

guestIdsおよびreservationIdsを取得できるため、それらを使用してReservationおよびGuestエンティティをクエリできます。

IDの代わりに予約およびゲストオブジェクトリストを取得する簡単な方法を見つけた場合、回答を更新します。

同様の答え

69
Devrim

実際には、 @ Devrim answerのようなidだけでなく、Guestリストを取得する可能性がもう1つあります。

最初に、GuestReservationの間の接続を表すクラスを定義します。

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

詳細については、 このブログ をご覧ください。

8
Nominalista

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

結合テーブルエンティティには、インデックス付きの複合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の名前を変更したことに注意してください。

このすべてのパフォーマンスを、よりフラットなエンティティ(連結なしの別のオプション)と比較してテストしませんでした。もしそうなら、答えを更新します。

0
Allan Veloso