我怎样才能代表Android Room的多对多关系?

时间:2017-06-05 04:47:42

标签: android kotlin android-room android-architecture-components

我如何表现与房间的多对多关系? 例如我有#34; Guest"和"预订"。预订可以有很多客人,客人可以参加许多预订。

这是我的实体定义:

@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的工作样本。

6 个答案:

答案 0 :(得分:64)

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

您可以使用额外的实体(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
)

您可以使用guestId列表进行预订。 (不是客人对象)

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

您还可以向访客显示reservationId的列表。 (不是预订对象)

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

由于您可以获得guestIdreservationId,因此您可以使用这些实体查询ReservationGuest个实体。

如果我找到一种简单的方法来获取预订和访客对象列表而不是他们的ID,我会更新我的答案。

Similar answer

答案 1 :(得分:6)

这是一种通过单个查询中的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(","));
    }
}

答案 2 :(得分:4)

通过介绍室内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>>

}

答案 3 :(得分:3)

实际上还有一种方法可以获得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
)

每次插入新的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>
}

要查看更多详细信息,请访问this blog

答案 4 :(得分:0)

对于连接表实体,我建议使用索引的复合ID:

new Vue({
   el: '#someEl',
   data: {
      file: null
   },
   methods: {
      fileChange (file) {
        this.file = file.files[0]
      }
   }
})

GuestDao.kt:

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

@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的数据连接为列字符串,最后通过来宾ID对行进行group_concat,并使用不同的分隔符将保留字符串进行连接,我们的转换器将负责将其重建为实体:

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 的名称,因为我认为Room不允许您使用SQLite保留名称。

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

答案 5 :(得分:-1)

基于上面的答案:https://stackoverflow.com/a/44428451/4992598只能在实体之间保留单独的字段名称 你可以返回模型(不仅仅是ids)。您所需要做的就是:

"responses": [
      "1": {
        "keywords": [
          "Sì",
          "Yes",
          "Oui"
        ],
        "nextQuestionId": 11
      },
      "2": {
        "keywords": [
          "No",
          "Not",
          "Non"
        ],
        "nextQuestionId": 12
      }
    ]

只要您不保留重复字段,就可以将实体嵌入到彼此中。因此,ReservationWithGuests类看起来像这样。

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

因此,此时您可以使用val guestIdList:List,因为您的ReservationGuest实体实际上将ID与实体模型映射。