具有多个非强制性过滤器的 Room DAO

Room DAO with multiple non-obligatory filters

在我的应用程序中,用户可以在每个组合中使用多个过滤器来过滤他们的数据(仅应用一个、多个或 none)。

在此之前,我只有一个过滤器,所以每次应用它时,我都在切换 DAO 方法。 现在我有 6 个过滤器,所以有几十种组合,因此不可能为每种组合创建一个方法。我也不能对我的数据库进行太多修改,因为它已经可供用户使用。

我当前的代码如下所示:

@Query("SELECT id, name, date FROM UserData")
fun getAll(): DataSource.Factory<Int, UserItem> //no filters

@Query("SELECT id, name, date FROM UserData WHERE name LIKE '%' || :search  || '%'")
fun getAllFiltered(query: String): DataSource.Factory<Int, UserItem> //one filter applied

有没有办法修改查询,以便所有过滤器组合都有一个方法?

更新:

这是我的数据class,我想过滤哪些实例:

@Entity(tableName = "UserItem")
data class UserItem(

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    val id: Long? = null,

    @ColumnInfo(name = "created_at")
    val createdAt: Date,

    @ColumnInfo(name = "is_uploaded")
    val isUploaded: Boolean,

    @ColumnInfo(name = "name")
    val name: String,

    @ColumnInfo(name = "item_sum")
    val sum: Int = 0,

    @ColumnInfo(name = "tags")
    val tags: List<String> = listOf(),
)

我想filter/check数值和布尔属性的相等性,检查列表属性是否包含指定的字符串。基本上,我希望能够过滤我能过滤的一切。如果不可能,我至少对一些过滤器感到满意。

这取决于您是否接受稍微复杂的查询,但我可能会这样做。创建这样的方法:

@Query("""
SELECT id, name, date FROM UserData WHERE 
(:nameQuery IS NULL OR name LIKE '%' || :nameQuery  || '%') AND
(:isUploaded IS NULL OR is_uploaded = :isUploaded) AND
(:sum IS NULL OR item_sum = sum)
""")
fun getAllFiltered(nameQuery: String?, isUploaded: Boolean?, sum: Int?
): DataSource.Factory<Int, UserItem>

如果特定字段没有筛选器,现在只需将 null 作为参数传递。

我不知道您是如何将 List<> 存储在数据库中的,但也许您可以像搜索字符串一样对该字段进行搜索(例如名称字段)

如果您想进一步提高搜索速度,可以为文本字段设置 FTS4 table,然后将 table 和 运行 字符串过滤器与 Match 连接起来和我在这里使用的其他过滤器。 (如果您需要在 FTS4 中搜索特殊字符,您必须为 table 设置分词器)

您可以使用Timb but there is a lot of null checks (complex where clauses affect performance of query), I would use RawQuery which is used for dynamic query. although it lacks the syntax highlighting that @Query provides. This Link提供的示例中提到的复杂查询。如果您使用 LiveData,请为重要的实体添加 observedEntities 属性 或 @RawQuery 注释。

根据@CommonsWare 的评论,我尝试使用 RawQuery 来实现我想要的。

首先想到的是创建过滤器数据 class 将来会保留 所有的过滤器。删除一个或添加多个真的很容易。

data class Filters(
    val query: String? = null,
    val isUploaded: Boolean? = null,
    // all the other filters
)

将构建查询的函数和 return 来自数据库的结果:

fun getAllFiltered(filters: Filters): DataSource.Factory<Int, UserItem> {
    val conditions = mutableListOf<Pair<String, Any?>>()
    with(filters) {
        query?.let { conditions.add("name LIKE '%' || ? || '%'" to it) }
        isUploaded?.let { conditions.add("is_uploaded = ${it.toInt()}" to null) }
        // "subqueries" to filter  specific field
    }

    if (conditions.isEmpty())
        return getAll()

    val conditionsMerged = conditions.joinToString(separator = " AND ") { it.first }
    val bindArgs = conditions.mapNotNull { it.second }

    val query = SimpleSQLiteQuery(
        "SELECT id, name, date FROM UserData WHERE $conditionsMerged",
        bindArgs.toTypedArray()
    )
    return getAllFiltered(query)
}

@RawQuery(observedEntities = [UserItem::class])
fun getAllFiltered(query: SupportSQLiteQuery): DataSource.Factory<Int, UserItem>

private fun Boolean.toInt() = if (this) 1 else 0

我不知道它会如何执行,因为必须在运行时构建查询(但从我所做的一些测试中,我没有注意到太多的性能损失),但优点是它是添加其他筛选器或删除现有筛选器非常容易,如果仅应用多个筛选器中的一个,创建的查询非常简单。