英文:
Calling a database query inside a transformation function
问题
我正在尝试在转换映射函数内部调用数据库查询,但遇到了各种错误。我的项目代码的简化版本如下。
**实体**
@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
)
**Dao**
@Query("SELECT category_name FROM CATEGORY_TABLE where categoryId= :categoryId")
suspend fun getCategoryNameById(categoryId: Long) : String
**Repository**
suspend fun getCategoryNameById(categoryId : Long) : String {
return databaseDao.getCategoryNameById(categoryId)
}
**ViewModel**
在ViewModel中,我查询了main_table并获取了数据。这些数据存储在*readAllData*变量中,它的类型是*LiveData<List<MainDatabase>>*。主表包含类别Id。我想使用*Transformation.map*获取类别名称。我在ViewModel中实现了以下内容。
suspend fun getName(categoryId: Long) : String{
val d : Deferred<String> = viewModelScope.async {
repository.getCategoryNameById(categoryId)
}
return d.await()
}
然后在初始化中我添加了
init{
..
..
..
listTransformed = Transformations.map(readAllData)
{ list ->
list.map {
viewModelScope.launch {
getName(it.mainCategoryOne)
}
}
}
}
我认为我需要使用*async*和*await*,因为我需要等待结果。这使得*getName*成为一个挂起函数,因此只能在协程中启动。然而,当像上面的代码中启动它时,我遇到了一个错误。
类型不匹配。
需要:String
找到:Job
如果我尝试在没有协程的情况下调用查询,应用程序会崩溃,因为我试图从主UI中进行数据库调用。在这里的正确方法是什么?或者有没有更简单的方法来解决这个问题?
谢谢
英文:
I am trying to call a database query inside a transformation map function and I am running to various errors. A simplified version of my project code is given below.
Entities
@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
)
Dao
@Query("SELECT category_name FROM CATEGORY_TABLE where categoryId= :categoryId")
suspend fun getCategoryNameById(categoryId: Long) : String
Repository
suspend fun getCategoryNameById(categoryId : Long) : String {
return databaseDao.getCategoryNameById(categoryId)
}
ViewModel
In the view model I queried the main_table and obtained the data. This was stored in readAllData variable which is of the type LiveData<List< MainDatabase >>. The main table contains the category Id. I want to use transformation.map to get the category name. I implemented the following in viewmodel.
suspend fun getName(categoryId: Long) : String{
val d : Deferred<String> = viewModelScope.async {
repository.getCategoryNameById(categoryId)
}
return d.await()
}
then in init I added
init{
..
..
..
listTransformed = Transformations.map(readAllData)
{ list ->
list.map {
viewModelScope.launch {
getName(it.mainCategoryOne)
}
}
}
}
I think I need to use the async and await since I need to wait for the result. Which makes getName a suspend function and thus can only launched in a coroutine. However, when launching it like in the code above, I get an error
Type Mismatch.
Required: String
Found : Job
If I try to call the query without a coroutine, the app crashes since I am attempting a database call from main UI. What would be the correct approach here? Or it there a simpler method to approach this problem?
Thank you
答案1
得分: 1
立即调用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,这样你可以在映射函数中调用挂起函数,然后如果你想坚持使用LiveData,可以将其转换回来。在内部映射中,你可以使用async
和awaitAll
并行映射列表的项目。我们使用coroutineScope { }
来做这个,使用子协程而不是同级协程。就像这样:
init {
..
..
..
listTransformed = readAllData.asFlow()
.mapLatest { list ->
coroutineScope {
list.map { async { getName(it.mainCategoryOne) } }.awaitAll()
}
}
.asLiveData()
}
编辑:我想我走岔了。因为这是一个数据库,如果你编写一个DAO函数来执行此操作,会更高效和简单。
假设在你的DAO中allData
是这样的:
@Query("SELECT * FROM main_table")
fun getAllData(): LiveData<List<MainDatabase>>
然后你可以编写另一个DAO函数如下:
// 返回在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>>
英文:
Immediately calling await()
on a Deferred means it was pointless to call async
in the first place. "Await" means wait for the asynchronous result synchronously, so why did you make it asynchronous in the first place? You can modify your function as follows to get the same behavior without the convoluted stuff:
suspend fun getName(categoryId: Long) : String{
return repository.getCategoryNameById(categoryId)
}
You can't mix and match LiveData and coroutines like that. Your map
lambda function is launching coroutines and returning the resulting coroutine Jobs, so the end result is a LiveData<List<Job>>
. A Job doesn't return anything, so it's useless for this purpose. You could use async
instead of launch
, and then you'd have a LiveData<List<Deferred<String>>>
and in the observer you could start a coroutine and call async()
on it to unwrap the values. Probably not what you want, since that's convoluted.
Instead, I would convert to a Flow, so you can call suspend functions in your mapping function, and then you can convert it back to a LiveData if you want to stick with using LiveData. In the inner map, you can use async
and awaitAll
to map the items of the list in parallel. We use coroutineScope { }
to do this with child coroutines instead of sibling coroutines. Like this:
init{
..
..
..
listTransformed = readAllData.asFlow()
.mapLatest { list ->
coroutineScope {
list.map { async { getName(it.mainCategoryOne) } }.awaitAll()
}
}
.asLiveData()
Edit: I think I got sidetracked. Since this is a database, it would be more efficient and simpler if you write a DAO function to do this.
Assuming allData
is something like this in your DAO:
@Query("SELECT * FROM main_table")
fun getAllData(): LiveData<List<MainDatabase>>
Then you could write another DAO function like this:
// 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>>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论