I have the following 3 entities:
#Entity(tableName = "PROPERTY",
indices = [
androidx.room.Index(value = ["id"], unique = true)
])
data class Property (
#Expose #PrimaryKey override val id: UUID = UUID.randomUUID(),
#SerializedName("type") #ColumnInfo(name = "type") val type: ItemPropertyType,
#SerializedName("code") #ColumnInfo(name = "code") val code: String,
#SerializedName("default_description") #ColumnInfo(name = "default_description") val defaultDescription: String?,
#SerializedName("description_id") #ColumnInfo(name = "description_id") val descriptionId: UUID?,
#SerializedName("default_property") #ColumnInfo(name = "default_property") val defaultProperty: Boolean,
#SerializedName("entity") #ColumnInfo(name = "entity") val entity: String = "ITEM"
)
#Entity(tableName = "PROPERTY_SET_DETAIL",
indices = [
Index(value = ["id"], unique = true),
Index(value = ["property_id"], unique = false)
],
foreignKeys = [
ForeignKey(entity = PropertySet::class, parentColumns = ["id"], childColumns = ["property_set_id"]),
ForeignKey(entity = Property::class, parentColumns = ["id"], childColumns = ["property_id"])])
data class PropertySetDetail (
#Expose #PrimaryKey override val id: UUID = UUID.randomUUID(),
#SerializedName("property_id") #ColumnInfo(name = "property_id") val propertyId: UUID,
#SerializedName("value") #ColumnInfo(name = "value") var value: String?,
#SerializedName("rank") #ColumnInfo(name = "rank") val rank: Int,
#SerializedName("property_set_id") #ColumnInfo(name = "property_set_id") val propertySetId: UUID
)
#Entity(tableName = "PROPERTY_SET",
indices = [Index(value = ["id"], unique = true), Index(value = ["properties_charset"], unique = false), Index(value = ["hashkey"], unique = false)])
data class PropertySet (
#Expose #PrimaryKey override val id: UUID = UUID.randomUUID(),
#SerializedName("item_id") #ColumnInfo(name = "item_id") val itemId: UUID,
#SerializedName("properties_charset") #ColumnInfo(name = "properties_charset") var propertiesCharset: String?,
#SerializedName("hashkey") #ColumnInfo(name = "hashkey") var hashkey: String?,
)
and also the following data classes that I need for the nested relationships among the 3 entities declared above:
data class PropertySetAndDetails(
#Embedded
val set: PropertySet,
#Relation(
entity = PropertySetDetail::class,
parentColumn = "id",
entityColumn = "property_set_id"
) val details: List<PropertyDetailAndProperty>,
)
data class PropertyDetailAndProperty(
#Embedded
val property: Property,
#Relation(
parentColumn = "id",
entityColumn = "property_id"
) val detail: PropertySetDetail
)
and the following DAO query function:
#Transaction
#Query("select * from property_set where item_id = :itemId")
suspend fun getSetsForItem(itemId: UUID): List<PropertySetAndDetails>
There is no way to make it work. I get the following build error:
error: There is a problem with the query: [SQLITE_ERROR] SQL error or
missing database (no such column: type)
I also declared the following convert functions for
ItemPropertyType:
#TypeConverter
fun toItemPropertyType(v: Byte?): ItemPropertyType {
return when (v) {
null -> ItemPropertyType.Unknown
else -> ItemPropertyType.getByValue(v)
}
}
#TypeConverter
fun fromItemPropertyType(t: ItemPropertyType?): Byte {
return when (t) {
null -> ItemPropertyType.Unknown.value
else -> t.value
}
}
Can someone explain me why this nested relationship doesn't work ? Thanks in advance.
Can someone explain me why this nested relationship doesn't work ?
I can't (let's call it Room's magic), but I can hint you how to make this work. Try to change your PropertyDetailAndProperty class to this:
data class PropertyDetailAndProperty(
#Embedded
val detail: PropertySetDetail,
#Relation(
parentColumn = "property_id",
entityColumn = "id"
) val property: Property
)
It seems nested class should contain entity you use in outer class ( PropertySetDetail in your case) beyond #Relation. Why? Well, I guess it just was developers' design.
Have you tried using the converter? Room cannot identify this data ("ItemPropertyType"). This is why you are getting this error.
Doc
Related
i have a room database with two entities and am many to many relation
but get a query error
im getting an error when using android room
error: The columns returned by the query does not have the fields [nameOfWorkout,dateOfWorkout,caloriesBurntInWorkout,workoutId] in com.example.sporttracker.room.relations.WorkoutWithExercises even though they are annotated as non-null or primitive. Columns returned by the query: [amountExercise,caloriesBurntProExercise,exerciseName,exerciseId]
public abstract androidx.lifecycle.LiveData<java.util.List<com.example.sporttracker.room.relations.WorkoutWithExercises>> fetchWorkoutWithExercises(long id);
my entities:
#Entity(tableName = "exercise_table")
data class Exercise(
#NonNull
#ColumnInfo(name = "amountExercise")
var amountExercise: String,
#NonNull
#ColumnInfo(name = "caloriesBurntProExercise")
var caloriesBurntProExercise: String,
#NonNull
#ColumnInfo(name = "exerciseName")
var exerciseName: String,
#PrimaryKey(autoGenerate = true)
#NonNull
#ColumnInfo(name = "exerciseId")
var exerciseId: Long = 0L
#Entity(tableName = "workout_table")
data class Workout(
#NonNull
#ColumnInfo(name = "nameOfWorkout")
var nameOfWorkout: String,
#NonNull
#ColumnInfo(name = "dateOfWorkout")
var dateOfWorkout: String,
#NonNull
#ColumnInfo(name = "caloriesBurntInWorkout")
var caloriesBurntInWorkout: String,
#PrimaryKey(autoGenerate = true)
#NonNull
#ColumnInfo(name = "workoutId")
var workoutId: Long = 0L
my dao:
#Query("""
SELECT *
FROM exercise_table
WHERE exerciseId = :id
""")
fun fetchWorkoutWithExercises(id: Long): LiveData<List<WorkoutWithExercises>>
my relation:
data class WorkoutWithExercises(
#Embedded val workout: Workout,
#Relation(
parentColumn = "workoutId",
entityColumn = "exerciseId",
associateBy = Junction(ExerciseWorkoutCrossRef::class)
)
val exercises: List<Exercise>
)
// region: entity
#Entity(
primaryKeys = ["exerciseId", "workoutId"],
foreignKeys = [
ForeignKey(
entity = Exercise::class,
parentColumns = ["exerciseId"],
childColumns = ["exerciseId"]
),
ForeignKey(
entity = Workout::class,
parentColumns = ["workoutId"],
childColumns = ["workoutId"]
)
]
)
//endregion
// region: data class
data class ExerciseWorkoutCrossRef(
#ColumnInfo(name = "exerciseId")
val exerciseId: Long,
#ColumnInfo(name = "workoutId", index = true)
val workoutId: Long
)
//endregion
I believe that you can resolve this by amending WorkoutWithExercises to define the relationship between exercise_table and the ExerciseWorkoutCrossRef table.
Foreign keys only define the constraint (rule) not a relationship (even though one implicitly exists)).
I believe changing WorkoutWithExercises to be like the following code will move you along:-
data class WorkoutWithExercises(
#Embedded val workout: Workout,
#Relation(
entity = ExerciseWorkoutCrossRef::class,
parentColumn = "workoutId",
entityColumn = "workoutId"
)
val workoutwithexercises: List<WorkoutWithExercises>,
#Relation(
entity = Exercise::class,
entityColumn = "exerciseId",
parentColumn = "workoutid",
associateBy = (Junction(ExerciseWorkoutCrossRef::class))
)
val exercises: List<Exercise>
)
i.e. the first #Relation describes the relationship between the Workout and the ExerciseWorkoutCrossRef table
the second #Relation describes the relationship between the ExerciseWorkoutCrossRef and the exercise_table.
(so all three tables are related as opposed to 2)
I believe what was happing is that you were getting the rows from the exercise_table BUT this could not be linked (via the ExerciseWorkoutCrossRef table) to get the appropriate workout_table rows and thus columns and hence the compile message.
I'm creating a small project with Room and three tables (jobs, tags, categories).
Essentially:
A job belongs to (or has) 1 and only category, but a job can have 0..N tags.
A category has 0..N jobs.
A tag has 0..N jobs.
I'm having some trouble trying to model all the data, especially when it comes to the relationships between the entities. More concretely, I have:
fun JobListing.toJobEntityList(): List<JobEntity> {
val jobEntityList = mutableListOf<JobEntity>()
this.jobs.take(50).forEach { job: Job ->
jobEntityList.add(
JobEntity(
jobId = job.id,
title = job.title,
url = job.url,
companyName = job.companyName,
jobType = job.jobType,
publicationDate = job.publicationDate,
relocation = job.candidateRequiredLocation,
salary = job.salary
)
)
}
return jobEntityList
}
This extension function is being called when I'm fetching the data from the network, so I can convert it to entities and store them in my DB.
I'm essentially creating a JobEntity, but a job should have 1 category and 0..N tags associated. The problem is that I don't know how to add that data related to the relationship between a job and its category and tags.
This is my JobEntity class:
#Entity(
tableName = "Jobs",
indices = [
Index("id", unique = true)
]
)
data class JobEntity(
#PrimaryKey #ColumnInfo(name = "id") val jobId: Int,
#ColumnInfo(name = "title") val title: String,
#ColumnInfo(name = "url") val url: String,
#ColumnInfo(name = "company_name") val companyName: String,
#ColumnInfo(name = "job_type") val jobType: String,
#ColumnInfo(name = "publication_date") val publicationDate: String,
#ColumnInfo(name = "candidate_required_location") val relocation: String,
#ColumnInfo(name = "salary") val salary: String
)
Thanks in advance!
Hi you should model your database like this:
Job_Category
Tags
Jobs (add category_id field and add 1 foreign key referencing to Category table)
Job_Tags (add tag_id, job_id fields and add 2 foreign keys referencing to Category and Jobs tables)
#Entity(tableName = "Category")
data class CategoryEntity(
#PrimaryKey #ColumnInfo(name = "id") val id: Int,
)
#Entity(tableName = "Tags")
data class TagEntity(
#PrimaryKey #ColumnInfo(name = "id") val id: Int,
)
#Entity(
tableName = "Jobs",
foreignKeys = [ForeignKey(entity = CategoryEntity::class, parentColumns = ["id"], childColumns = ["categoryid"])]
)
data class JobEntity(
#PrimaryKey #ColumnInfo(name = "id") val id: Int,
#ColumnInfo(name = "categoryid", index = true) val categoryid: Int,
// ... other fields
)
#Entity(
tableName = "JobTags",
foreignKeys = [
ForeignKey(entity = TagEntity::class, parentColumns = ["id"], childColumns = ["tagId"]),
ForeignKey(entity = JobEntity::class, parentColumns = ["id"], childColumns = ["jobId"]),
]
)
data class JobTagEntity(
#PrimaryKey #ColumnInfo(name = "id") val id: Int,
#ColumnInfo(name = "tagId", index = true) val tagId: Int,
#ColumnInfo(name = "jobId", index = true) val jobId: Int,
)
I have three tables and I have a many to many relationship here:
Contact table:
#Entity(tableName = "T_Contacts")
class Contact(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "_id") var id: Long,
#ColumnInfo(name = "phoneNumber")
var phoneNumber: String = "",
#ColumnInfo(name = "name")
var name: String = "",
#ColumnInfo(name = "active")
var active: Int = 1
#ColumnInfo(name = "contactId")
var contactId: Long = -1
#ColumnInfo(name = "phoneContactId")
var phoneContactId: String = ""
}
Email table:
#Entity(tableName = "T_EmailAddress")
class EmailAddress(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "_id")
var id : Long,
#ColumnInfo(name="emailAddress")
var emailAddress: String,
#ColumnInfo(name="active")
var active : Int) {
}
And the associative table:
#Entity(tableName = "T_EmailAddressContact",
foreignKeys = [ForeignKey(
entity = Contact::class,
parentColumns = arrayOf("contactId"),
childColumns = arrayOf("contactId"),
onDelete=ForeignKey.CASCADE),
ForeignKey(
entity = EmailAddress::class,
parentColumns = arrayOf("emailId"),
childColumns = arrayOf("emailId"),
onDelete=ForeignKey.CASCADE)]
)
class EmailAddressContactCrossRef(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "_id")
var id: Long,
#ColumnInfo(name="contactId")
var contactId: Long,
#ColumnInfo(name="emailId")
var emailId : Long,
#ColumnInfo(name="active")
var active : Int ) {
How can I get a list of all the email contacts using relations? I tried:
data class EmailAddressContact(
#Embedded val contact: EmailAddress,
#Relation(parentColumn = "contactId",
entityColumn = "contactId",
associateBy = Junction(EmailAddressContactCrossRef::class)
)
val listContacts: List<Contact>
)
but this only gets me a list of all contacts for a certain email address. I want something like this:
data class EmailAddressContact {
val id: Long,
val emailAddress: EmailAddress,
val contact: Contact,
val active: Boolean
}
by calling a query:
#Transaction
#Query("SELECT * FROM T_EmailAddressContact")
fun getAllEmailAddressAndContacts(): List<EmailAddressContact>
According to your EmailAddressContact structure it seems you don't need to use junction, just relations. Try this way:
data class EmailAddressContact(
#Embedded val emailAddressContactCrossRef: EmailAddressContactCrossRef, // <- there you'll get "id" and "active" fields
#Relation(
parentColumn = "contactId",
entityColumn = "contactId"
)
val contact: Contact,
#Relation(
parentColumn = "emailId",
entityColumn = "_id"
)
val emailAddress: EmailAddress
)
and query you mentioned in your question should fit
I have the following Kotlin entities for Room ver. 2.2.5.
PARENT ENTITY
#Entity(tableName = "ITEM",
indices = [Index(value = ["id"], unique = true), Index(value = ["code"], unique = true), Index(value = ["status"], unique = false)]
)
data class Item (
#PrimaryKey #ColumnInfo(name = "id") override val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "code") #NotNull val code: String,
#ColumnInfo(name = "valid") val valid: Boolean = true,
#ColumnInfo(name = "value") val value: Double?,
#ColumnInfo(name = "price") val price: Double?,
#ColumnInfo(name = "default_description") val defaultDescription: String?,
#ColumnInfo(name = "description") val description: String?
)
CHILD ENTITY
#Entity(tableName = "LOCATION",
indices = [Index(value = ["id"], unique = true), Index(value = ["code"], unique = true)]
)
data class Location (
#ColumnInfo(name = "id") #PrimaryKey override val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "code") val code: String,
#ColumnInfo(name = "latitude") val latitude: Double?,
#ColumnInfo(name = "longitude") val longitude: Double?,
#ColumnInfo(name = "default_description") val defaultDescription: String?
)
JUNCTION ENTITY
#Entity(
tableName = "ITEM_LOCATION_L",
primaryKeys = [
"item_id", "location_id"
],
foreignKeys = [
ForeignKey(entity = Item::class, parentColumns = ["id"], childColumns = ["item_id"]),
ForeignKey(entity = Location::class, parentColumns = ["id"], childColumns = ["location_id"])
],
indices = [
Index("id"),
Index("item_id"),
Index("location_id")])
data class ItemLocationLink (
#ColumnInfo(name = "id") override val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "item_id") val itemId: UUID, /** Item ID - parent entity */
#ColumnInfo(name = "location_id") val locationId: UUID, /** Location ID - child entity */
#ColumnInfo(name = "quantity") val quantity: Double /** Quantity of the item in the referenced location */
)
RESULT CLASS
class ItemLocationRelation {
#Embedded
lateinit var item: Item
#Relation(
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(value = ItemLocationLink::class, parentColumn = "item_id", entityColumn = "location_id")
) lateinit var locations: List<Location>
}
DAO INTERFACE
#Dao
interface ItemLocationLinkDao {
#Transaction
#Query("SELECT * FROM ITEM WHERE id = :itemId")
fun getLocationsForItem(itemId: UUID): List<ItemLocationRelation>
}
DATABASE TYPE CONVERTER
class DBTypesConverter {
#TypeConverter
fun fromUUID(uid: UUID?): String? {
if (uid != null)
return uid.toString()
return null
}
#TypeConverter
fun toUUID(str: String?): UUID? {
if (str != null) {
return UUID.fromString(str)
}
return null
}
#TypeConverter
fun fromDate(d: Date?): Long? {
return d?.time
}
#TypeConverter
fun toDate(l: Long?): Date? {
return if (l != null) Date(l) else null
}
}
When I call getLocationsForItem I get in return an instance of ItemLocationRelation with a valid Item but no child objects. I've checked the generated code and there is no sign of the Junction class. The generated code behaves like it is not a many-to-many relation, the Junction class is completely ignored, I can even specify a fake class in the #Junction attribute of the relation and the result would be exactly the same without errors.
If I add a function to the DAO class that returns the results of the following query:
select * from item
inner join item_location_l as link on link.item_id = item.id
inner join LOCATION as l on link.location_id = l.id
where item.id = '99a3a64f-b0e6-e911-806a-68ecc5bcbe06'
I get 2 rows as expected. So the SQLite database is ok. Please help.
So, in the end the problem was really subtle. For reasons I cannot explain the project I was working on had the following gradle settings (app gradle):
kapt "android.arch.persistence.room:compiler:2.2.5"
I replaced it with
kapt "androidx.room:room-compiler:2.2.5"
And then everything was fine. The hard-coded query generated by the plugin now contains the JOIN and it works....
Given
#Entity(
tableName = "state",
foreignKeys = arrayOf(
ForeignKey(entity = Foo::class, parentColumns = arrayOf("id"), childColumns = arrayOf("foo_id")),
ForeignKey(entity = Bar::class, parentColumns = arrayOf("id"), childColumns = arrayOf("bar_id"))
)
)
data class State(
#PrimaryKey(autoGenerate = true) val id: Long = 1
) {
#ColumnInfo(name = "foo_id")
var fooId: Long? = null
#ColumnInfo(name = "bar_id")
var barId: Long? = null
}
#Entity(tableName = "foo")
open class Foo(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
open val id: Long,
#ColumnInfo(name = "foo")
val foo: String?,
)
#Entity(tableName = "bar")
open class Bar(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
open val id: Long,
#ColumnInfo(name = "bar")
val bar: String?,
)
I am trying to create a Join POJO to store the results of the query:
class FooBar(
#Embedded
val foo: Foo,
#Embedded
val bar: Bar
)
And my failed attempt at a query:
#Query("SELECT foo.*, bar.* FROM foo, bar JOIN state ON foo.id == state.foo_id JOIN bar ON bar.id == session.bar_id ")
fun getFooBar(): LiveData<FooBar>
However I get an error when compiling. Do I need to deconflict the id fields in foo and bar since they are named the same?
I have tried with a prefix, no luck:
class FooBar(
#Embedded(prefix = "foo_")
val foo: Foo,
#Embedded(prefix = "bar_")
val bar: Bar
)
Any ideas?
Here is an example that might help you.
#Query("SELECT RoomArticle.*, RoomBranch.id AS 'RoomBranch_id', RoomBranch.name AS 'RoomBranch_name' "
Data entity:
public class RoomArticleOfBranch {
#Embedded
public RoomArticle article;
#Embedded(prefix = "RoomBranch_")
public RoomBranch branch;
Notice I provide prefix both in query and in #Embedded.
As you're trying Embedded try to change the query as follows
#Dao
interface TestDao {
#Query("SELECT foo.id as foo_id,bar.id as bar_id,bar.bar as bar_bar FROM foo, bar JOIN state ON foo_id = state.foo_id and bar_id = state.bar_id")
fun getFooBar(): LiveData<FooBar>
}
There are two things need to be fixed.
You need to rename the column name during the select query.
fixing your joining query.