kotlin 在转换函数中调用数据库查询

5cnsuln7  于 2023-04-21  发布在  Kotlin
关注(0)|答案(1)|浏览(141)

我试图在转换Map函数中调用数据库查询,并且遇到了各种错误。下面给出了我的项目代码的简化版本。

实体

@Entity(tableName = "category_table")
data class CategoryDatabase(

    @PrimaryKey(autoGenerate = true)
    var categoryId : Long = 0L,

    @ColumnInfo(name="category_name")
    var categoryName : String = ""
)

.

@Entity(tableName = "main_table")
data class MainDatabase(

    @PrimaryKey(autoGenerate = true)
    val mainId : Long = 0L,

    @ColumnInfo(name="category_one")
    val mainCategoryOne : Long = 0L
)

@Query("SELECT category_name FROM CATEGORY_TABLE where categoryId= :categoryId")
    suspend fun getCategoryNameById(categoryId: Long) : String

仓库

suspend fun getCategoryNameById(categoryId : Long) : String {
        return databaseDao.getCategoryNameById(categoryId)
}

视图模型

在视图模型中,我查询了main_table并获得了数据。这存储在 readAllData 变量中,该变量的类型为 LiveData〈List< MainDatabase >〉。主表包含类别ID。我想使用transformation.map来获取类别名称。我在视图模型中实现了以下内容。

suspend fun getName(categoryId: Long) : String{
        val d : Deferred<String> = viewModelScope.async {
            repository.getCategoryNameById(categoryId)
        }
        return d.await()
    }

然后在init中添加

init{
    ..
    ..
    ..

listTransformed = Transformations.map(readAllData)
        { list ->
            list.map {
                   viewModelScope.launch {
                                         getName(it.mainCategoryOne)
                   }
            }

        }
    }

我想我需要使用async和await,因为我需要等待结果。这使得 getName 成为一个挂起函数,因此只能在协程中启动。然而,当像上面的代码一样启动它时,我得到一个错误

Type Mismatch.
 Required: String 
 Found : Job

如果我尝试在没有协程的情况下调用查询,应用程序会崩溃,因为我正在尝试从主UI调用数据库。这里正确的方法是什么?或者有更简单的方法来解决这个问题?
谢谢你

eyh26e7m

eyh26e7m1#

立即在Deferred上调用await()意味着在第一时间调用async是没有意义的。“Await”意味着同步等待异步结果,那么为什么你首先要使它异步呢?你可以如下修改你的函数以获得相同的行为,而不需要复杂的东西:

suspend fun getName(categoryId: Long) : String{
    return repository.getCategoryNameById(categoryId)
}

你不能像这样混合和匹配LiveData和协程。你的map lambda函数是启动协程并返回结果协程Jobs,所以最终结果是LiveData<List<Job>>。Job不返回任何东西,所以它在这个目的上是无用的。你可以使用async而不是launch,然后你会有一个LiveData<List<Deferred<String>>>,在观察者中你可以启动一个协程,并在上面调用async()来解包这些值。可能不是你想要的,因为那是复杂的。
相反,我会转换为Flow,这样你就可以在Map函数中调用suspend函数,然后如果你想坚持使用LiveData,你可以将它转换回LiveData。在内部Map中,你可以使用asyncawaitAll来并行Map列表中的项。我们使用coroutineScope { }来对子协程而不是兄弟协程来完成这一任务。就像这样:

init{
    ..
    ..
    ..

    listTransformed = readAllData.asFlow()
        .mapLatest { list -> 
            coroutineScope {
                list.map { async { getName(it.mainCategoryOne) } }.awaitAll()
            }
        }
        .asLiveData()

编辑:我想我有点跑题了。因为这是一个数据库,如果你写一个DAO函数来做这件事,会更高效,更简单。
假设allData在你的DAO中是这样的:

@Query("SELECT * FROM main_table")
fun getAllData(): LiveData<List<MainDatabase>>

然后你可以像这样写另一个DAO函数:

// Returns list of category names that are referenced in main_table
@Query("SELECT c.category_name FROM main_table AS m INNER JOIN category_table AS c ON m.category_one=c.categoryId")
fun getAllUsedCategoryNames(): LiveData<List<String>>

相关问题