Android 相同列名的 Room Multimap 问题
Android Room Multimap issue for the same column names
如 official documentation 所述,Android Room 数据库最好使用 Multimap return 类型。
对于下一个非常简单的示例,它无法正常工作!
@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)
(我相信很多开发人员在他们的 table 中有 _id
主键)
现在道中class:
@Query(
"SELECT * FROM user " +
"JOIN book ON user._id = book.userId"
)
fun allUserBooks(): Flow<Map<User, List<Book>>>
数据库tables:
最后,当我 运行 上面的查询时,我得到的是:
虽然它应该有 2 个条目,因为对应的 table 中有 2 个用户。
PS。我现在使用的是最新的 Room 版本,Version 2.4.0-beta02.
PPS。问题在于如何生成 UserDao_Impl.java:
所有 _id
列都有相同的索引。
有机会在这里做点什么吗? (而不是切换到中间数据 classes)。
all the _id columns have the same index there.
Is there a chance to do something here?
是,使用唯一的列名例如
@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 是图书的 _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) {
}
- 为了方便和简洁,没有使用
Flow
,上面是 运行 在主线程上。
那么结果就是我相信你所期待的:-
额外
What if we already have the database structure with a lot of "_id" fields?
然后你需要做出一些决定。
你可以
- 进行迁移以重命名列以避免 ambiguous/duplicate 列名称。
- 使用替代 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>>
- 所以 user._id 输出的名称与 Alt_User POJO
相同
- 其他列专门输出(尽管您可以根据 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 有多个同名列,则 Room 不会重写查询,因为它还没有办法区分哪一个是必要的。
@RewriteQueriesToDropUnusedColumns
并没有消除警告。
如果使用 :-
var mapping = dao.allUserBooksAlt() //<<<<<<<<<< BREAKPOINT HERE
for(m: Map.Entry<Alt_User,List<Alt_Book>> in mapping) {
}
会导致:-
- 可能还有其他选择。
但是,我建议通过使用迁移将列重命名为所有具有唯一名称的列来一劳永逸地解决这个问题。例如
如 official documentation 所述,Android Room 数据库最好使用 Multimap return 类型。
对于下一个非常简单的示例,它无法正常工作!
@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)
(我相信很多开发人员在他们的 table 中有 _id
主键)
现在道中class:
@Query(
"SELECT * FROM user " +
"JOIN book ON user._id = book.userId"
)
fun allUserBooks(): Flow<Map<User, List<Book>>>
数据库tables:
最后,当我 运行 上面的查询时,我得到的是:
虽然它应该有 2 个条目,因为对应的 table 中有 2 个用户。
PS。我现在使用的是最新的 Room 版本,Version 2.4.0-beta02.
PPS。问题在于如何生成 UserDao_Impl.java:
所有 _id
列都有相同的索引。
有机会在这里做点什么吗? (而不是切换到中间数据 classes)。
all the _id columns have the same index there. Is there a chance to do something here?
是,使用唯一的列名例如
@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 是图书的 _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) {
}
- 为了方便和简洁,没有使用
Flow
,上面是 运行 在主线程上。
那么结果就是我相信你所期待的:-
额外
What if we already have the database structure with a lot of "_id" fields?
然后你需要做出一些决定。
你可以
- 进行迁移以重命名列以避免 ambiguous/duplicate 列名称。
- 使用替代 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>>
- 所以 user._id 输出的名称与 Alt_User POJO 相同
- 其他列专门输出(尽管您可以根据 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 有多个同名列,则 Room 不会重写查询,因为它还没有办法区分哪一个是必要的。
@RewriteQueriesToDropUnusedColumns
并没有消除警告。
- 由于请注意,如果 Room 有多个同名列,则 Room 不会重写查询,因为它还没有办法区分哪一个是必要的。
如果使用 :-
var mapping = dao.allUserBooksAlt() //<<<<<<<<<< BREAKPOINT HERE
for(m: Map.Entry<Alt_User,List<Alt_Book>> in mapping) {
}
会导致:-
- 可能还有其他选择。
但是,我建议通过使用迁移将列重命名为所有具有唯一名称的列来一劳永逸地解决这个问题。例如