Как указать внешние связи со встроенной сущностью

#android #android-room

#Android #android-комната

Вопрос:

Данный ответ изменил встроенный тип на тип внешнего ключа, я этого не ищу.

У меня есть таблица student и адрес встроенного типа.

Схема учащегося

  ---- ------ ------------ 
| id | name | address_id |
 ---- ------ ------------ 
|  1 | John |          1 |
|  2 | Jane |          2 |
 ---- ------ ------------ 
  

Схема адресов

  ---- ------------------ 
| id |     addressLine  |
 ---- ------------------ 
|  1 | 123 Acme Street  |
|  2 | 456 Beach Street |
 ---- ------------------ 
  

Объект Student

 @Entity(tableName = "student")
data class Student(
     @PrimaryKey var id: Long, 
     var name: String?, 
     @Embedded var address: Address // can't change, given answer changed it
)
  

Адресная сущность

 @Entity(tableName = "address")
data class Address(
     @PrimaryKey var id: Long, 
     val addressLine: String
)
  

Интерфейс DAO

 @Dao
interface StudentDao {

    @Query("SELECT * FROM student WHERE id = :id INNER JOIN address ON student.address_id = address.id")
    fun findById(id: Long): Student
}
  

Проблема: в настоящее время невозможно заполнить встроенный адрес учащегося.

Ответ №1:

Обычным способом для вашего варианта использования является использование дополнительного класса POJO (не объекта) для получения объединенного результата из обеих таблиц.

Итак, сущности:

 @Entity(tableName = "student")
data class Student(
     @PrimaryKey var id: Long, 
     var name: String?, 
     var address_id: Long // <- you can make it foreign key in addition 
)

@Entity(tableName = "address")
data class Address(
     @PrimaryKey var id: Long, 
     val addressLine: String
)
  

и вспомогательный класс POJO:

 data class StudentWithAdress( // <-- you can change the class name to more descriptive one 
     @Embedded var student: Student 
     @Embedded var address: Address 
)
  

Затем ваш dao:

 @Dao
interface StudentDao {

@Query("SELECT * FROM student WHERE id = :id INNER JOIN address ON student.address_id = address.id")
    fun findById(id: Long): StudentWithAdress
}
  

ОБНОВЛЕНО

Второй способ — использовать взаимно однозначное отношение @вместо этого, используя соединения SQLite

Этот метод использует одни и те же сущности, но запрос и вспомогательный класс немного отличаются:

 data class StudentWithAdress(
     @Embedded var student: Student 
     @Relation(
         parentColumn = "addressId",
         entityColumn = "id"
    )
    var address: Address 
)
  

и запрос был бы проще:

 @Transaction
@Query("SELECT * FROM student WHERE id = :id")
    fun findById(id: Long): StudentWithAdress
}
  

Заключение

  1. Student класс должен включать ТОЛЬКО addressId (не весь Address объект), и вы используете отдельный класс StudentWithAdress , который имеет оба Student и Address . Итак, чтобы вставить значение Student , вам следует вставить его addressId из соответствующей Address таблицы. Это обычный и правильный способ.
  2. Оба описанных способа похожи, вы можете выбрать любой. Лично я предпочитаю способ отношений.
  3. Технически можно использовать только две сущности без вспомогательного класса (и хранить все Address содержимое внутри Stident сущности), но я думаю, что это нарушает принцип нормализации реляционной таблицы, и я не хочу включать это в свой ответ, поскольку это противоречит шаблону.

ОБНОВЛЕНО 2 (анти-шаблон)

Я не рекомендую это, но если вы настаиваете, вы можете использовать следующую схему:

 @Entity(tableName = "address")
data class Address(
     @PrimaryKey var idAddress: Long,  // <-- changed
     val addressLine: String
)

@Entity(tableName = "student")
data class Student(
     @PrimaryKey var id: Long, 
     var name: String?, 
     @Embedded var address: Address 
)
  

Как это работает? @Embedded означает, что на самом деле таблица Sqlite Student включает в себя все поля Address . В вашем случае в реальной таблице Sqlite будет 4 столбца — id, name, idAddress, AddressLine (именно поэтому необходимо изменить Address имя первичного ключа, поскольку не может быть полей с одинаковым именем).
Комната немного скрывает это, и вы работаете с Student объектом через address поле. Почему это плохо? Давайте посмотрим на сценарий:

  1. Вы сохраняете Address с id = 1, AddressLine = «Некоторый адрес # 1».
  2. Вы устанавливаете этот адрес в каком Student -либо объекте и сохраняете его. Под капотом Sqlite в Student таблице будет содержать значения id = 1 , addressLine = Some address #1 .
  3. Затем по какой-то причине вы меняете addressLine на Address «Некоторый адрес # 2».
  4. Но в Student таблице все еще есть старое значение addressLine . Это ошибка. Или вы должны сохранить это изменение в Student таблице, и это тоже плохо.

Комментарии:

1. Привет, спасибо за ответ, не могли бы вы также отредактировать и добавить версию «один к одному», это то, что я искал

2. это должен быть объект всего адреса, а не этот -> var address_id: Long // <- you can make it foreign key in addition

3. Обновил мой ответ. Что касается this has to be whole Address object, not this , я думаю, что это неправильный путь, я попытался объяснить это в разделе заключения ответа.

4. Спасибо за повторную попытку, я ценю. На самом деле я ищу решение без вспомогательного класса, сглаживаю данные учащихся на корневом уровне и адрес как встроенный. Не могли бы вы сделать еще одну попытку, и вы можете пометить ее как анти-шаблон.

5. Для повторения у меня здесь только два класса: Student и Address. data class Student( @PrimaryKey var id: Long, var name: String?, @Embedded var address: Address )