Поиск и фильтрация Android Room с использованием отношений, запрос нескольких таблиц
Репо находится ниже, если вы хотите сделать пул-реквест, включите комментарии.
Я создал базу данных комнат для своего PokemonApp. Я хочу иметь возможность фильтровать и искать в базе данных на основе имени покемона и типов покемонов.
У меня есть таблица для моей сущности Pokemon, таблица для сущности PokemonType и таблица соединений для сущности PokemonTypeJoin, у меня также есть класс данных PokemonWithTypes, который встраивает сущность Pokemon и определяет связь между этим и списком сущностей PokemonType.
Сущность покемона:
@TypeConverters(RoomStringListConverter::class)
@Entity
data class Pokemon(
@NotNull
@PrimaryKey
@ColumnInfo(name = POKEMON_ID)
var id: Int,
@ColumnInfo(name = POKEMON_NAME)
var name: String,
@ColumnInfo(name = POKEMON_URL)
var url: String,
@ColumnInfo(name = POKEMON_WEIGHT)
val weight: Int,
@ColumnInfo(name = POKEMON_HEIGHT)
val height: Int,
@ColumnInfo(name = POKEMON_SPECIES)
var species: String,
@ColumnInfo(name = POKEMON_MOVES)
val moves: List<String>
)
const val POKEMON_ID: String = "pokemon_id"
const val POKEMON_NAME: String = "pokemon_name"
const val POKEMON_URL: String = "pokemon_url"
const val POKEMON_HEIGHT: String = "pokemon_height"
const val POKEMON_WEIGHT: String = "pokemon_weight"
const val POKEMON_MOVES: String = "pokemon_moves"
const val POKEMON_SPECIES: String = "pokemon_species"
PokemonType сущность:
@Entity
data class PokemonType (
@NotNull
@PrimaryKey
@ColumnInfo(name = POKEMON_TYPE_ID)
var id: Int,
@ColumnInfo(name = POKEMON_TYPE_NAME)
var name: String,
@ColumnInfo(name = POKEMON_TYPE_SLOT)
var slot: Int
)
const val POKEMON_TYPE_ID: String = "type_id"
const val POKEMON_TYPE_NAME: String = "type_name"
const val POKEMON_TYPE_SLOT: String = "type_slot"
PokemonTypesJoin entity:
@Entity(primaryKeys = [POKEMON_ID, POKEMON_TYPE_ID])
class PokemonTypesJoin(
@NotNull
@ColumnInfo(name = POKEMON_ID, index = true)
val pokemon_id: Int,
@NotNull
@ColumnInfo(name = POKEMON_TYPE_ID, index = true)
val pokemon_type_id: Int
)
const val POKEMON_ID: String = "id"
const val POKEMON_TYPE_ID: String = "type_id"
PokemonWithTypes класс
data class PokemonWithTypes(
@Embedded
val pokemon: Pokemon,
@Relation(
parentColumn = Pokemon.POKEMON_ID,
entity = PokemonType::class,
entityColumn = PokemonType.POKEMON_TYPE_ID,
associateBy = Junction(
value = PokemonTypesJoin::class,
parentColumn = PokemonTypesJoin.POKEMON_ID,
entityColumn = PokemonTypesJoin.POKEMON_TYPE_ID
)
)
val types: List<PokemonType>
)
учитывая эту структуру, я могу получить и выполнить поиск по pokemon_name по всем типам PokemonWithTypes, используя следующий запрос:
@Transaction
@Query("SELECT * FROM pokemon WHERE pokemon_name LIKE :search ORDER BY pokemon_id ASC")
fun getPokemonWithTypes(search: String?): LiveData<List<PokemonWithTypes>>
но как я могу теперь добавить фильтр (список строк), который возвращает только PokemonWithTypes, когда любой из PokemonWithTypes.types соответствует заданному типу в списке фильтров?
Итак, даны 3 покемона (некоторые данные удалены для краткости)
PokemonWithTypes(pokemon=Pokemon(id=1, name=bulbasaur, types=[PokemonType(id=4, name=poison, slot=2), PokemonType(id=12, name=grass, slot=1)])
PokemonWithTypes(pokemon=Pokemon(id=4, name=charmander, types=[PokemonType(id=10, name=fire, slot=2), PokemonType(id=12, name=grass, slot=1)])
PokemonWithTypes(pokemon=Pokemon(id=7, name=squirtle, types=[PokemonType(id=11, name=water, slot=2), PokemonType(id=12, name=grass, slot=1)])
В настоящее время я получаю всех покемонов и могу выполнять поиск по pokemon_name, но я хотел бы иметь возможность отображать только типы воды или только типы травы, любые идеи приветствуются,
Я пробовал просто фильтровать строку вместо списка строк с таким запросом
@Transaction
@Query("SELECT * FROM pokemon, pokemonType WHERE type_name LIKE :filter AND pokemon_name LIKE :search ORDER BY pokemon_id ASC")
fun getPokemonWithTypes(search: String?, filter: String): LiveData<List<PokemonWithTypes>>
но это не сработало
вы можете проверить полную здесь https://github.com/martipello/PokeApp/tree/add_filters
Ответы
Я думаю, что аннотация @Relation не предназначена для этого варианта использования. Он предназначен только для возврата ВСЕХ связанных типов, а не отфильтрованного подмножества. Думаю, у вас есть 3 варианта:
- Просто профильтровать с Котлин:
pokemonWithTypes.filter { it.types.contains("GRASS") }
. Я предполагаю, что у вас не более 10000 записей покемонов, поэтому производительность не является проблемой. - Напишите запрос на соединение. Я думаю, что это больше усилий для незначительного прироста производительности.
- Используйте представления базы данных согласно: https://issuetracker.google.com/issues/65509934. Это более статично, и вам придется написать представление для каждого типа.