#android-room #multimap
#android-room #multimap
Вопрос:
Как указано в официальной документации, предпочтительнее использовать возвращаемый тип Multimap для базы данных Android Room.
В следующем очень простом примере это работает некорректно!
@Entity
data class User(@PrimaryKey(autoGenerate = true) val _id: Long = 0, val name: String)
@Entity
data class Book(@PrimaryKey(autoGenerate = true) val _id: Long = 0, val bookName: String, val userId: Long)
(Я полагаю, что у многих разработчиков есть _id
первичный ключ в их таблицах)
Теперь, в классе Dao:
@Query(
"SELECT * FROM user "
"JOIN book ON user._id = book.userId"
)
fun allUserBooks(): Flow<Map<User, List<Book>>>
Наконец, когда я выполняю приведенный выше запрос, вот что я получаю:
Хотя в нем должно быть 2 записи, так как в соответствующей таблице есть 2 пользователя.
PS. На данный момент я использую последнюю версию Room, версию 2.4.0-beta02.
PPS. Проблема в том, как UserDao_Impl.java генерируется:
все _id
столбцы имеют там один и тот же индекс.
Есть ли шанс что-то сделать здесь? (вместо переключения на промежуточные классы данных).
Ответ №1:
все столбцы _id имеют одинаковый индекс. Есть ли шанс что-то сделать здесь?
Да, используйте уникальные имена столбцов, например
@Entity
data class User(@PrimaryKey(autoGenerate = true) val userid: Long = 0, val name: String)
@Entity
data class Book(@PrimaryKey(autoGenerate = true) valbookid: Long = 0, val bookName: String, val useridmap: Long)
- как используется в примере ниже.
или
@Entity
data class User(@PrimaryKey(autoGenerate = true) @ColumnInfo(name="userid")val _id: Long = 0, val name: String)
@Entity
data class Book(@PrimaryKey(autoGenerate = true) @ColumnInfo(name="bookid")val _id: Long = 0, val bookName: String, val @ColumnInfo(name="userid_map")userId: Long)
В противном случае, как вы, возможно, заметили, Room использует значение последнего найденного столбца с дублированным именем, а идентификатор пользователя — это значение столбца _id книги.
Используя вышеизложенное и реплицируя ваши данные с помощью :-
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
var currentUserId = dao.insert(User(name = "Eugene"))
dao.insert(Book(bookName = "Eugene's book #1", useridmap = currentUserId))
dao.insert(Book(bookName = "Eugene's book #2", useridmap = currentUserId))
dao.insert(Book(bookName = "Eugene's book #3", useridmap = currentUserId))
currentUserId = dao.insert(User(name = "notEugene"))
dao.insert(Book(bookName = "not Eugene's book #4", useridmap = currentUserId))
dao.insert(Book(bookName = "not Eugene's book #5", useridmap = currentUserId))
var mapping = dao.allUserBooks() //<<<<<<<<<< BREAKPOINT HERE
for(m: Map.Entry<User,List<Book>> in mapping) {
}
- для удобства и краткости a
Flow
не использовался, и вышеупомянутое было запущено в основном потоке.
Тогда результат — это то, что, я полагаю, вы ожидаете :-
Дополнительные
Что, если у нас уже есть структура базы данных с большим количеством полей «_id»?
Тогда вам нужно принять несколько решений.
Вы могли бы
- выполните миграцию для переименования столбцов, чтобы избежать неоднозначных / повторяющихся имен столбцов.
- используйте альтернативные POJO в сочетании с соответствующим изменением имен выходных столбцов извлечения
например, есть :-
data class Alt_User(val userId: Long, val name: String)
и
data class Alt_Book (val bookId: Long, val bookName: String, val user_id: Long)
наряду с :-
@Query("SELECT user._id AS userId, user.name, book._id AS bookId, bookName, user_id "
"FROM user JOIN book ON user._id = book.user_id")
fun allUserBooksAlt(): Map<Alt_User, List<Alt_Book>>
- итак, пользователь._id выводится с именем в соответствии с POJO Alt_User
- другие столбцы выводятся специально (хотя вы могли бы использовать * в соответствии с allUserBookAlt2)
:-
@Query("SELECT *, user._id AS userId, book._id AS bookId "
"FROM user JOIN book ON user._id = book.user_id")
fun allUserBooksAlt2(): Map<Alt_User, List<Alt_Book>>
- то же, что и у allUserBooksAlt, но также имеет дополнительные столбцы
- вы получите предупреждение
warning: The query returns some columns [_id, _id] which are not used by any of [a.a.so70190116kotlinroomambiguouscolumnsfromdocs.Alt_User, a.a.so70190116kotlinroomambiguouscolumnsfromdocs.Alt_Book]. You can use @ColumnInfo annotation on the fields to specify the mapping. You can annotate the method with @RewriteQueriesToDropUnusedColumns to direct Room to rewrite your query to avoid fetching unused columns. You can suppress this warning by annotating the method with @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns returned by the query: _id, name, _id, bookName, user_id, userId, bookId. public abstract java.util.Map<a.a.so70190116kotlinroomambiguouscolumnsfromdocs.Alt_User, java.util.List<a.a.so70190116kotlinroomambiguouscolumnsfromdocs.Alt_Book>> allUserBooksAlt2();
- Обратите внимание, что Room не будет переписывать запрос, если в нем есть несколько столбцов с одинаковыми именами, поскольку у него еще нет способа определить, какой из них необходим.
@RewriteQueriesToDropUnusedColumns
предупреждение не устраняется.
- Обратите внимание, что Room не будет переписывать запрос, если в нем есть несколько столбцов с одинаковыми именами, поскольку у него еще нет способа определить, какой из них необходим.
при использовании :-
var mapping = dao.allUserBooksAlt() //<<<<<<<<<< BREAKPOINT HERE
for(m: Map.Entry<Alt_User,List<Alt_Book>> in mapping) {
}
Приведет к :-
- возможно, другие варианты.
Тем не менее, я бы предложил устранить проблему раз и навсегда, используя миграцию для переименования столбцов, чтобы все они имели уникальные имена. например
Комментарии:
1. Да, это помогает, спасибо. Однако я надеялся, что Room сможет сделать это как-то своими силами.
2. Что, если у нас уже есть структура базы данных с большим количеством полей «_id»?
3. @GoltsevEugene обновил ответ, но, вероятно, не очень хорошие новости.
4. Есть похожие мысли. Большое спасибо и особенно за эту вещь @Rewrite, может быть, когда-нибудь пригодится!
5. Проблема была исправлена несколько дней назад. Я полагаю, это должно быть доступно в будущей версии Room. issuetracker.google.com/issues/201306012