60

我如何表示与 的“多对多”关系Room?我的列名也一样。

例如我有GuestReservationReservationcan have many Guest's 和 aGuest可以是许多 Reservations 的一部分。

这是我的实体定义:

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

上面我得到编译器错误:

> Cannot figure out how to read this field from a cursor.

我无法找到@Relation.

4

6 回答 6

89

我有一个类似的问题。这是我的解决方案。

您可以使用额外的实体 ( ) 来保持和ReservationGuest之间的关系。GuestReservation

@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,因此您可以使用它们查询ReservationGuest实体。

如果我找到一种简单的方法来获取 Reservation 和 Guest 对象列表而不是它们的 id,我会更新我的答案。

类似的答案

于 2017-06-08T06:42:21.957 回答
45

通过在房间中引入Junction,您可以轻松处理多对多关系。

正如@Devrim 所说,您可以使用一个额外的实体(ReservationGuest)来保持Guest 和Reservation 之间的关系(也称为关联表或联结表或连接表)。

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

}
于 2019-10-17T04:09:23.637 回答
19

实际上还有另一种获得Guest列表的可能性,而不仅仅是@Devrim答案中的id。

首先定义代表 和 之间连接的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
)

每次插入 newReservation时,都必须插入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>
}

要查看更多详细信息,请访问此博客

于 2018-05-27T09:31:31.733 回答
9

这是一种在单个查询中通过 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 并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(","));
    }
}
于 2017-12-31T21:49:38.233 回答
-1

对于连接表实体,我建议使用复合 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 对行进行分组连接,将预订字符串与不同的分隔符连接起来,我们的转换器将负责将其重建为一个实体:

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 保留名称。

与拥有更多扁平实体(另一个没有连接的选项)相比,我没有测试所有这些的性能。如果我这样做,我会更新我的答案。

于 2018-08-24T14:55:55.620 回答
-1

根据上面的答案:https ://stackoverflow.com/a/44428451/4992598只有在实体之间保留单独的字段名称,您才能返回模型(不仅仅是 id)。您需要做的就是:

@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 实体实际上将 id 映射到实体模型。

于 2017-11-15T16:18:17.967 回答