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
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 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
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 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....
I've been looking around and couldn't find any explanation why this was happening.
I've tried opening my db file in apps like sqliteman and running "INSERT INTO" queries myself and it works just fine that way. Yet, whatever Room does doesn't seem to be working, it doesn't crash or anything, it just doesn't insert anything.
So basically I have 2 tables connected in a m-n relation via a join table:
1) Song table
#Entity(tableName = RoomDbConstants.TABLE_NAME_SONGS,
foreignKeys = [ForeignKey(entity = Album::class,
parentColumns = [RoomDbConstants.COLUMN_ALBUM_ARTIST_NAME, RoomDbConstants.COLUMN_ALBUM_NAME],
childColumns = [RoomDbConstants.COLUMN_SONG_ARTIST_NAME, RoomDbConstants.COLUMN_SONG_ALBUM_NAME],
onDelete = ForeignKey.CASCADE),
ForeignKey(entity = Genre::class,
parentColumns = [RoomDbConstants.COLUMN_GENRE_NAME],
childColumns = [RoomDbConstants.COLUMN_SONG_GENRE_NAME],
onDelete = ForeignKey.CASCADE)])
data class Song(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_ID)
val id: Long?,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_DATA)
val songData: String,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_NAME)
val name: String,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_ALBUM_NAME)
val albumName: String,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_ARTIST_NAME)
val artistName: String,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_GENRE_NAME)
val genreName: String,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_TRACK_NUMBER)
val trackNumber: Int = RoomDbConstants.TRACK_NUMBER_DEFAULT,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_YEAR_PUBLISHED)
val yearPublished: Int = RoomDbConstants.YEAR_PUBLISHED_NONE,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_START_TIME_IN_MILLIS)
val startTimeInMillis: Int,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_END_TIME_IN_MILLIS)
val endTimeInMillis: Int,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_DURATION_IN_MILLIS)
val durationInMillis: Int,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONG_COVER_DATA)
val coverData: String?)
2) Playlist table
#Entity(tableName = RoomDbConstants.TABLE_NAME_PLAYLISTS,
indices = [Index(value = [RoomDbConstants.COLUMN_PLAYLIST_NAME], unique = true)])
data class Playlist(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = RoomDbConstants.COLUMN_PLAYLIST_ID)
val id: Long?,
#ColumnInfo(name = RoomDbConstants.COLUMN_PLAYLIST_NAME)
val name: String,
#ColumnInfo(name = RoomDbConstants.COLUMN_PLAYLIST_COVER_DATA)
val coverData: String?)
3) SongsPlaylistsLink (The table that connects between Songs and Playlists)
#Entity(tableName = RoomDbConstants.TABLE_NAME_SONGS_PLAYLISTS_LINKS,
primaryKeys = [RoomDbConstants.COLUMN_SONGS_PLAYLISTS_LINK_SONG_ID,
RoomDbConstants.COLUMN_SONGS_PLAYLISTS_LINK_PLAYLIST_ID],
foreignKeys = [ForeignKey(entity = Song::class,
parentColumns = [RoomDbConstants.COLUMN_SONG_ID],
childColumns = [RoomDbConstants.COLUMN_SONGS_PLAYLISTS_LINK_SONG_ID],
onDelete = ForeignKey.CASCADE),
ForeignKey(entity = Playlist::class,
parentColumns = [RoomDbConstants.COLUMN_PLAYLIST_ID],
childColumns = [RoomDbConstants.COLUMN_SONGS_PLAYLISTS_LINK_PLAYLIST_ID],
onDelete = ForeignKey.CASCADE)],
indices = [Index(value = [RoomDbConstants.COLUMN_SONGS_PLAYLISTS_LINK_SONG_ID,
RoomDbConstants.COLUMN_SONGS_PLAYLISTS_LINK_PLAYLIST_ID], unique = true)])
data class SongsPlaylistsLink(
#ColumnInfo(name = RoomDbConstants.COLUMN_SONGS_PLAYLISTS_LINK_SONG_ID)
val songId: Long,
#ColumnInfo(name = RoomDbConstants.COLUMN_SONGS_PLAYLISTS_LINK_PLAYLIST_ID)
val playlistId: Long)
Insert function:
#Insert
fun insert(songsPlaylistsLink: SongsPlaylistsLink)
So here's what I tried to do:
I already have songs and playlists in the database.
So let's say I have a song with an ID of 1 and a playlist with an ID of 1.
When I do insert(SongsPlaylistsLink(1,1)) nothing happens.
The table is still empty.
Any help on why this happens will be greatly appreciated!
Looks like running db.beginTransaction() and db.endTransaction() before and after the insert statements respectively doesn't work all that well. Removing it fixed it for me.