Я получил этот код в своем DAO:
@Query("select Conversation.*, User.* from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
fun selectAllForOverview(conversationUuid: UUID): LiveData<List<ConversationSelectAllForOverview>>
Это ConversationSelectAllForOverview
data class ConversationSelectAllForOverview(
@Embedded(prefix = "arg0")
val arg0: DbConversation,
@Embedded(prefix = "arg1")
val arg1: DbUser
)
Я читал, что мне нужно аннотировать свои поля с помощью prefix
, чтобы избавиться от ошибок, когда они имеют общие имена полей. Я получаю эту ошибку, и я не знаю, как я могу ее удалить. Я на 100% уверен, что все столбцы доступны, так как DbConversation
и DbUser
просто генерируются из базы данных. Как я могу решить эту проблему? DbConversation
и DbUser
делят несколько столбцов, см. определение DbConversation
здесь: https://gist.github.com/Jasperav/381243e7b3cf387bfc0e9f1343f9faeb. DbUser
выглядит так же.
error: The columns returned by the query does not have the fields
[conversationUuid,createdBy,tsCreated,distanceMapped,showOnMap,showOnOverview,allowMessagesByInRangeRegularUsers,allowMessagesByOutOfRangeRegularUsers,stillReadableForOutOfRangeRegularUsers,freedomInReplies,title,subject,likes,latitude,longitude,hasPassword,isSubscribed,showOnMapScreen,isLiked,bypassChecks,isHidden,nsfw,currentDirectEvents,totalDirectEventsAfterLastJoin,subscriptions,userUuid,username,karma,tsCreated,allowsPrivateChats,allowsNsfw,thisUserBlockedCurrentUser,incomingFriendshipRequest,outstandingFriendshipRequest,friends,bio,appRoleMapped]
in entity.ConversationSelectAllForOverview even though they are
annotated as non-null or primitive. Columns returned by the query:
[conversationUuid,createdBy,tsCreated,distanceMapped,showOnMap,showOnOverview,allowMessagesByInRangeRegularUsers,allowMessagesByOutOfRangeRegularUsers,stillReadableForOutOfRangeRegularUsers,freedomInReplies,title,subject,likes,avatar,latitude,longitude,hasPassword,isSubscribed,showOnMapScreen,isLiked,bypassChecks,isHidden,conversationReportReasonMapped,nsfw,currentDirectEvents,totalDirectEventsAfterLastJoin,lastReadConversationEventPk,mostRecentConversationEventUuid,relevance,subscriptions,userUuid,username,karma,tsCreated,allowsPrivateChats,allowsNsfw,avatar,currentUserBlockedThisUserTsCreated,thisUserBlockedCurrentUser,searchScreenScore,recentSearchedTsCreated,userReportReasonMapped,incomingFriendshipRequest,outstandingFriendshipRequest,friends,bio,appRoleMapped]
public abstract androidx.lifecycle.LiveData<java.util.List<entity.ConversationSelectAllForOverview>>
selectAllForOverview(@org.jetbrains.annotations.NotNull()
Взгляните на свои классы DbConversation и DbUser и убедитесь, что эти классы @Embeddable
. Если нет, поместите аннотацию в класс.
Другое решение — снова посмотреть на ошибку. В нем говорится, что запрос возвращает набор столбцов, которых нет в вашем DTO (ConversationSelectAllForOverview); поля вашего DTO - это объекты, а не поля столбцов. Вы можете создать интерфейс, получающий все те столбцы, которые возвращает БД:
interface nameInterface(
val conversationUuid: Any,
val createdBy: Any,
val tsCreated: Any,
val distanceMapped: Any,
val showOnMap: Any,
val showOnOverview: Any,
val allowMessagesByInRangeRegularUsers: Any,
val allowMessagesByOutOfRangeRegularUsers: Any,
val stillReadableForOutOfRangeRegularUsers: Any,
val freedomInReplies: Any,
val title: Any,
val subject: Any,
val likes: Any,
val latitude: Any,
val longitude: Any,
val hasPassword: Any,
val isSubscribed: Any,
val showOnMapScreen: Any,
val isLiked: Any,
val bypassChecks: Any,
val isHidden: Any,
val nsfw: Any,
val currentDirectEvents: Any,
val totalDirectEventsAfterLastJoin: Any,
val subscriptions: Any,
val userUuid: Any,
val username: Any,
val karma: Any,
val tsCreated: Any,
val allowsPrivateChats: Any,
val allowsNsfw: Any,
val thisUserBlockedCurrentUser: Any,
val incomingFriendshipRequest: Any,
val outstandingFriendshipRequest: Any,
val friends: Any,
val bio: Any,
val appRoleMapped: Any)
Проблема
Вы добавляете префикс к столбцам в аннотации @Embedded
в сочетании с выходными столбцами без префикса в соответствии с запросом.
Например, класс ConversationSelectAllForOverview ожидает найти столбец с именем arg0conversationUuid в выходных данных/результатах запроса, но в запросе есть только столбец talkUuid.
Исправление
Вместо использования select Conversation.*, User.* ....
вам нужно использовать
select Conversation.conversationUuid AS arg0conversationUuid, Conversation.createdBy AS arg0createdBy ....
AS
присвоение выходному столбцу разговора Uuid псевдонима arg0conversationUuid и т. д.то есть для каждого столбца в обеих таблицах вы должны использовать псевдоним фактического столбца с его префиксом.
например используя (только частично скорректировано): -
@Query("select " +
"Conversation.conversationUuid AS arg0conversationUuid" +
", Conversation.createdBy AS arg0createdBy" +
", Conversation.tsCreated AS arg0tsCreated" +
", Conversation.distanceMapped AS arg0distanceMapped" +
/* .... */
", User.* from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
fun selectAllForOverview(conversationUuid: UUID): LiveData<List<ConversationSelectAllForOverview>>
The columns returned by the query does not have the fields [showOnMap,showOnOverview ....
Альтернативное исправление (непроверенное и зависящее от библиотек Room 2.5.0)
Другим решением, которое может работать с комнатой 2.5.0 (не проверено), будет использование аннотации @Relation
вместо аннотации @Embedded
для ребенка (детей). например без каких-либо других изменений, кроме: -
data class ConversationSelectAllForOverview(
@Embedded/*(prefix = "arg0")*/
val arg0: DbConversation,
@Relation(
entity = DbUser::class,
parentColumn = "createdBy",
entityColumn = "userUuid"
)
val arg1: DbUser
)
а затем с помощью: -
@Transaction
@Query("SELECT * FROM Conversation WHERE conversationUUid=:conversationUuid")
fun selectAllForOverview(conversationUuid: UUID): LiveData<List<ConversationSelectAllForOverview>>
Затем он успешно компилируется (опять же не запускается, см. ниже). т. е. нет проблем с дублированными столбцами (опять же см. ниже).
Вы также можете использовать (обратите внимание, что имя функции отличается, чтобы можно было скомпилировать оба): -
@Transaction
@Query("select Conversation.*,User.* from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
fun selectAllForOverviewAlt(conversationUuid: UUID): LiveData<List<ConversationSelectAllForOverview>>
@Relationship
для последующего создания объекта DbUser с помощью последующего запроса (отсюда и @Transaction).ПРИМЕЧАНИЕ Комната используется для получения последнего найденного значения для выходного столбца с таким же именем и применения этого последнего значения ко всем полям с таким именем. Сообщается, что это было исправлено в версии 2.5.0. Однако это не было подтверждено. Таким образом, вы можете получить непредвиденные результаты, поэтому, если вы выберете этот подход, вы должны подтвердить, что значения соответствуют ожидаемым (т. е. проверить значения столбцов/полей с одинаковыми именами).
Дополнительная демонстрация
Ниже приведена рабочая демонстрация, основанная на коде класса question DbConversation. НО, для упрощения был составлен другой код, и, кроме того, многие поля были закомментированы. LiveData закомментирован и .allowMainThreadQueries
для упрощения демонстрации.
В демоверсии используются оба исправления, а для @Relation
как исходный запрос, так и предложенный более удобный запрос.
Точка останова отладки использовалась для демонстрации наборов из 3 возвратов.
Код базы данных (все, что нужно для демонстрации, плюс кое-что, что не нужно комментировать). Следует отметить, что большая часть кода может отличаться, были сделаны простые предположения.
:-
@Entity(
tableName = "Conversation",
primaryKeys = ["conversationUuid"],
indices = [/*Index(value = ["nsfw", "relevance"]), Index(value = ["isSubscribed"]),*/ Index(value = ["createdBy"])/*, Index(
value = ["avatar"]
), Index(value = ["mostRecentConversationEventUuid"])*/],
foreignKeys = [/*ForeignKey(
entity = DbConversationEventMostRecent::class,
childColumns = ["mostRecentConversationEventUuid"],
parentColumns = ["conversationEventUuid"],
onDelete = SET_NULL,
onUpdate = CASCADE,
), ForeignKey(
entity = DbMedia::class,
childColumns = ["avatar"],
parentColumns = ["mediaUuid"],
onDelete = CASCADE,
onUpdate = NO_ACTION,
), */ForeignKey(
entity = DbUser::class,
childColumns = ["createdBy"],
parentColumns = ["userUuid"],
onDelete = CASCADE,
onUpdate = NO_ACTION,
)]
)
data class DbConversation(
@ColumnInfo(typeAffinity = ColumnInfo.TEXT)
val conversationUuid: UUID,
@ColumnInfo(typeAffinity = ColumnInfo.TEXT)
val createdBy: UUID,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val tsCreated: Long,
@ColumnInfo(typeAffinity = ColumnInfo.TEXT)
val distanceMapped: ConversationDistance,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val showOnMap: Boolean,
/*
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val showOnOverview: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val allowMessagesByInRangeRegularUsers: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val allowMessagesByOutOfRangeRegularUsers: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val stillReadableForOutOfRangeRegularUsers: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val freedomInReplies: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.TEXT)
val title: String,
@ColumnInfo(typeAffinity = ColumnInfo.TEXT)
val subject: String,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val likes: Long,
@ColumnInfo(typeAffinity = ColumnInfo.TEXT)
val avatar: UUID?,
@ColumnInfo(typeAffinity = ColumnInfo.REAL)
val latitude: Double,
@ColumnInfo(typeAffinity = ColumnInfo.REAL)
val longitude: Double,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val hasPassword: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val isSubscribed: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val showOnMapScreen: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val isLiked: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val bypassChecks: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val isHidden: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.TEXT)
val conversationReportReasonMapped: ConversationReportReason?,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val nsfw: Boolean,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val currentDirectEvents: Long,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val totalDirectEventsAfterLastJoin: Long,
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
val lastReadConversationEventPk: ConversationEventPk?,
@ColumnInfo(typeAffinity = ColumnInfo.TEXT)
val mostRecentConversationEventUuid: UUID?,
@ColumnInfo(typeAffinity = ColumnInfo.REAL)
val relevance: Double?,
@ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
val subscriptions: Long
*/
)
@Dao
interface AllDAOs {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(dbUser: DbUser): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(dbConversation: DbConversation): Long
@Query("select " +
"Conversation.conversationUuid AS arg0conversationUuid" +
", Conversation.createdBy AS arg0createdBy" +
", Conversation.tsCreated AS arg0tsCreated" +
", Conversation.distanceMapped AS arg0distanceMapped" +
", Conversation.showOnMap AS arg0showOnMap" +
/* .... */
",User.userUuid AS arg1userUuid" + /*?????? made up/incomplete/asssumed */
",User.userName AS arg1userName" +
" from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
fun selectAllForOverviewOld(conversationUuid: UUID): /*LiveData<*/List<OldConversationSelectAllForOverview>/*>*/
@Query("SELECT * FROM Conversation WHERE conversationUuid=:conversationUuid")
fun selectConversationByUuid(conversationUuid: UUID): List<DbConversation>
@Query("SELECT * FROM Conversation WHERE conversationUuid=:conversationUuid")
fun selectAllForOverview(conversationUuid: UUID): /*LiveData<*/List<ConversationSelectAllForOverview>/*>*/
@Query("select Conversation.*,User.* from Conversation join User on Conversation.createdBy = User.userUuid where conversationUuid = :conversationUuid")
fun selectAllForOverviewAlt(conversationUuid: UUID): /*LiveData<*/List<ConversationSelectAllForOverview>/*>*/
}
data class ConversationDistance(
val blah: String
)
data class ConversationReportReason(
val blah: String
)
data class ConversationEventPk(
val blah: ByteArray
)
class RoomTypeConverters {
@TypeConverter
fun fromConversationDistanceToJSON(conversationDistance: ConversationDistance): String = Gson().toJson(conversationDistance)
@TypeConverter
fun toConversationDistanceFromJSON(json: String): ConversationDistance = Gson().fromJson(json,ConversationDistance::class.java)
@TypeConverter
fun fromConversationReportReasonToJSON(conversationReportReason: ConversationReportReason): String = Gson().toJson(conversationReportReason)
@TypeConverter
fun toConversationReportReasonFromJSON(json: String): ConversationReportReason = Gson().fromJson(json,ConversationReportReason::class.java)
@TypeConverter
fun fromConversationEventPkToByteArray(conversationEventPk: ConversationEventPk): ByteArray = ByteArray(100)
@TypeConverter
fun toConversationEventPkFromByteArray(byteArray: ByteArray): ConversationEventPk = ConversationEventPk(byteArray)
}
@Entity(tableName = "User")
data class DbUser(
@PrimaryKey
val userUuid: UUID,
val userName: String
)
@Entity
data class DbMedia(
@PrimaryKey
val mediaUuid: UUID,
val mediaName: String
)
@Entity
data class DbConversationEventMostRecent(
@PrimaryKey
val conversationEventUuid: UUID
)
data class ConversationSelectAllForOverview(
@Embedded/*(prefix = "arg0")*/
val arg0: DbConversation,
@Relation(
entity = DbUser::class,
parentColumn = "createdBy",
entityColumn = "userUuid"
)
val arg1: DbUser
)
data class OldConversationSelectAllForOverview(
@Embedded(prefix = "arg0")
val arg0: DbConversation,
@Embedded(prefix = "arg1")
val arg1: DbUser
)
@TypeConverters(value = [RoomTypeConverters::class])
@Database(entities = [DbMedia::class,DbUser::class,DbConversationEventMostRecent::class,DbConversation::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDAOs(): AllDAOs
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance=Room.databaseBuilder(context,TheDatabase::class.java,"the_database")
.allowMainThreadQueries() /* For brevity of demo */
.build()
}
return instance as TheDatabase
}
}
}
Код активности: -
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDAOs()
val u1uuid = UUID(10L,10L)
val u2uuid = UUID(11L,12L)
val c1uuid = UUID(20L,20L)
val c2uuid = UUID(21L,21L)
val c3uuid = UUID(22L,10L)
val c4uuid = UUID(15L,10L)
dao.insert(DbUser(u1uuid,"Fred"))
dao.insert(DbUser(u2uuid,"Mary"))
dao.insert(DbConversation(c1uuid,u1uuid,0, ConversationDistance("blah blah blah"),true))
dao.insert(DbConversation(c2uuid,u2uuid,0,ConversationDistance("uhm uhm uhm"),true))
dao.insert(DbConversation(c3uuid,u1uuid,0,ConversationDistance("meh meh meh"),true))
dao.insert(DbConversation(c4uuid,u1uuid,0,ConversationDistance("good good good"),true))
val c1 = dao.selectConversationByUuid(c1uuid)
val c2 = dao.selectConversationByUuid(c2uuid)
val c3 = dao.selectConversationByUuid(c3uuid)
val c4 = dao.selectConversationByUuid(c4uuid)
val t1c1 = dao.selectAllForOverviewOld(c1uuid)
val t1c2 = dao.selectAllForOverview(c1uuid)
val t1c3 = dao.selectAllForOverviewAlt(c1uuid)
val t2c1 = dao.selectAllForOverviewOld(c2uuid)
val t2c2 = dao.selectAllForOverview(c2uuid)
val t2c3 = dao.selectAllForOverviewAlt(c2uuid)
if (t1c1==null) t1c1==null /*<<<<<<<<<< BREAKPOINT HERE >>>>>>>>>>*/
}
}
При запуске из новой установки окно отладки с t1 ?? а т2?? расширен: -
Как видно, все 3 запроса дают одинаковый результат. Таким образом, самым простым решением было бы
@Relation
вместо @Embedded
и используйте более краткий запрос, просто извлекая соответствующие DbConversation(s).Комната действительно ужасна, что заставляет меня делать это. Почему он не десериализует столбцы по индексу, а по имени? В любом случае, я исправил эту проблему, добавив префикс столбцов, как было предложено... это работает, но да, это некрасиво :(
@ J.Doe, если вы просто используете чистые объекты (также известные как @Relation
), тогда нет необходимости а) кодировать JOIN или б) использовать префиксы. Лично я всегда использовал бы уникальные имена столбцов, возможно, с именем таблицы как частью имени. Не говорю, что вы должны, просто говорите, что можете.
Embeddable
не существует