I have a many to many relationship Room database with three tables:
First one :
data class Name(
#PrimaryKey(autoGenerate = true)
var nameId : Long = 0L,
#ColumnInfo(name = "name")
var name : String = "",
#ColumnInfo(name = "notes")
var notes: String=""
)
Second:
#Entity(tableName = "tags_table")
data class Tag(
#PrimaryKey(autoGenerate = true)
var tagId : Long = 0L,
#ColumnInfo(name = "tag_name")
var tagName : String = ""
)
Third:
#Entity(
tableName = "tagInName_table",
primaryKeys = ["nameId", "tagId"],
foreignKeys = [
ForeignKey(
entity = Name::class,
parentColumns = ["nameId"],
childColumns = ["nameId"]
),
ForeignKey(
entity = Tag::class,
parentColumns = ["tagId"],
childColumns = ["tagId"]
)
]
)
data class TagInName(
#ColumnInfo(name = "nameId")
var nameId: Long = 0L,
#ColumnInfo(name = "tagId")
var tagId: Long = 0L
)
The data class I use for a return object in a Query:
data class NameWithTags(
#Embedded
val name: Name,
#Relation(
parentColumn = "nameId",
entityColumn = "tagId",
associateBy = Junction(value = TagInName::class)
)
val listOfTag : List<Tag>
)
This is how I query to get all NamesWithTags:
#Query("SELECT * FROM names_table")
#Transaction
fun getNamesWithTags() : LiveData<List<NameWithTags>>
So the thing I need to do is, I need to Query to return LiveData<List<NameWithTags>> where every NamesWithTags has a list which contains the Tag ID that I Query for.
From my interpretation of what you say you need to do, then :-
#Transaction
#Query("SELECT names_table.* FROM names_table JOIN tagInName_table ON names_table.nameId = tagInName_table.nameId JOIN tags_table ON tagInName_table.tagId = tags_table.tagId WHERE tags_table.tagId=:tagId ")
fun getNameWithTagsByTagId(tagId: Long): LiveData<List<NamesWithTags>>
Note the above is in-principle code and has not been compiled or tested, so it may contain some errors.
A NameWithTags will contain ALL related tags whcih should be fine according to (where every NamesWithTags has a list which contains the Tag ID ), if you wanted just certain Tags in the List of Tags then it's a little more complex, this is explained in a recent answer at Android Room query with condition for nested object
I have five tables in my database: AREA, AREA_TYPE, SAMPLE, PACK, UNIT
#Entity(tableName = "AREA")
data class AreaEntity(
#PrimaryKey val id:String,
val title:String,
#ColumnInfo(name = "area_type_id") val areaTypeId:Int,
#ColumnInfo(name = "is_active") val isActive:Boolean
)
#Entity(tableName = "AREA_TYPE")
data class AreaTypeEntity(
#PrimaryKey val id:String,
val title:String,
#ColumnInfo(name = "title") val parentAreaId : String
)
#Entity(tableName = "SAMPLE")
data class SampleEntity(
#PrimaryKey val id:String,
val title:String,
)
#Entity(tableName = "PACK")
data class PackEntity(
#PrimaryKey val id:String,
val title:String,
)
#Entity(tableName = "UNIT")
data class UnitEntity(
#PrimaryKey val id:String,
#ColumnInfo(name = "sample_id") val parentAreaId : String,
#ColumnInfo(name = "area_id") val areaId:Int,
#ColumnInfo(name = "pack_type_id") val packTypeId: Int,
#ColumnInfo(name = "is_active") val isActive:Boolean
)
UNIT table has three foreign keys : sample_id, area_id, pack_id
Every area has one-to-one relationship with area type.
I have an AreaPOJO for Area-AreaType Relationship:
data class AreaPOJO (
#Embedded val areaEntity : AreaEntity
#Relation (
parentColumn = "area_id",
entityColumn = "id"
)
val areaTypeEntity : AreaTypeEntity
)
Visual view of tables (https://i.stack.imgur.com/bXzl5.png)
So I assume that I will have a POJO for UNIT for the Relationships like this:
data class UnitPOJO (
#Embedded val unitEntity : UnitEntity
#Relation (
parentColumn = "area_id",
entityColumn = "id"
)
val areaEntity : AreaEntity
#Relation (
parentColumn = "pack_id",
entityColumn = "id"
)
val packEntity : PackEntity
#Relation (
parentColumn = "sample_id",
entityColumn = "id"
)
val sampleEntity : SampleEntity
)
With this POJO, I can get AreaEntity,SampleEntity,UnitEntity but I can't get AreaTypeEntity for UnitPOJO.
When I use AreaPOJO instead of AreaEntity, I have a compilation error which tells me to use "prefix" for AreaPOJO. When I use prefix, this time AreaPOJO gives an error that it can't find the column names for relationship.
So I am stuck :)
Briefly I need all the fields from all five tables for this query :
"SELECT * FROM UNIT
INNER JOIN AREA ON UNIT.AREA_ID = AREA.ID
INNER JOIN AREA_TYPE ON AREA.AREA_TYPE_ID = AREA_TYPE.ID
INNER JOIN SAMPLE ON UNIT.SAMPLE_ID = SAMPLE.ID
INNER JOIN PACK ON UNIT.PACK_ID = PACK.ID"
First the use of prefix, this is an option to circumvent the ambiguous column names (e.g. which id column is the correct one to use? (rhetorical)) BUT you would then have to play around with the queries to include AS (implicitly or explicitly) to rename the extracted columns.
I would suggest that using unique column names is the way to avoid such ambiguities.
Onto the grandparent/grandchild.
In short you are close BUT you retrieve an AreaPOJO (Area with Type) not an AreaEntity, but you then have to tell Room to use the AreaEntity class (as that is the class used to ascertain the columns for the AreaEntity and then the #relation in the AreaPOJO knows to get the inderlying AreaType).
So although untested but successfully compiled consider the following:-
#Entity(tableName = "UNIT")
data class UnitEntity(
#PrimaryKey val id:String,
#ColumnInfo(name = "sample_id") val parentAreaId : String,
#ColumnInfo(name = "area_id") val areaId:Int,
#ColumnInfo(name = "pack_type_id") val packTypeId: Int,
#ColumnInfo(name = "is_active") val isActive:Boolean
)
#Entity(tableName = "AREA_TYPE")
data class AreaTypeEntity(
#PrimaryKey #ColumnInfo(name = "area_type_id") val id:String, //<<<<< unique name
val title:String,
#ColumnInfo(name = "area_type_title") val parentAreaId : String
)
data class AreaPOJO(
#Embedded val areaEntity : AreaEntity,
#Relation(
parentColumn = "area_type_id", //<<<<< changed accrodingly
entityColumn = "area_type_id" //<<<<< changed accordingly
)
val areaTypeEntity : AreaTypeEntity
)
data class UnitPOJO (
#Embedded val unitEntity : UnitEntity,
#Relation (
entity = AreaEntity::class, //<<<<< ADDED
parentColumn = "area_id",
entityColumn = "area_id"
)
val areaWithAreaType : AreaPOJO,
#Relation (
parentColumn = "pack_type_id",
entityColumn = "pack_id"
)
val packEntity : PackEntity,
#Relation (
parentColumn = "sample_id",
entityColumn = "sample_id"
)
val sampleEntity : SampleEntity
)
Notice the pack_type_id and pack_id in the UnitPOJO as opposed to sample_id and sample_id for the sample reference/relationship.
I would suggest considering using unique names e.g. pack_type_id is referencing/mapping the relationship between the unit and the pack, perhaps naming the column pack_id_map in the Unit. Thus the column names are then more descriptive and also unique. The downside is that there is more coding.
using the above the #Query could be :-
#Transaction
#Query("SELECT * FROM UNIT")
fun getUnitsWithRelations(): List<UnitPOJO>
Obviously adjusted according if using Flow/Live Data and so on.
Saying that, the above is inefficient as when Room processes an #Relation it builds and underlying query per #Relation that gets ALL the children from the parent (I believe on a per parent basis). In your case, it appears that you have 1 to many relationships thus #Embedded can be used BUT the query has to be more complex.
Working Example
The following is a working example based upon your code that
uses both #Relation and #Embedded resolutions
The #Relation POJO's are UnitPOJO and AreaPOJO
The #Embedded versions are prefixed with Alternative
Adds data (3 rows into each table, except 5 Units)
Extracts the Unit's and the related data using both alternatives
includes Foreign Key constraints that enforce referential and maintain integrity see https://sqlite.org/foreignkeys.html
It should be noted that some changes have been made as I believe that you some unusual and at a guess unecesassry relationships. e.g. You appear to have Area relate to AreaType both ways when only one is required. That is an Area will have an AreaType as a parent but if an ArearType also has an Area as a parent then you get the chicken and egg scenario.
The assumption has been made that an Area has one of the many available AreaTypes as a parent.
First the classes (see comments) :-
#Entity(
tableName = "AREA",
/* Enforces/Maintains referential Integrity */
/* i.e does not allow orphans */
foreignKeys = [
ForeignKey(
entity = AreaTypeEntity::class,
parentColumns = ["area_type_id"],
childColumns = ["area_type_id_map" ],
onDelete = ForeignKey.CASCADE /* ????? */,
onUpdate = ForeignKey.CASCADE /* ????? */
)
]
)
data class AreaEntity(
#PrimaryKey #ColumnInfo(name = "area_id")val id:String, //<<<<< unique name
#ColumnInfo(name = "area_title") val title:String,
#ColumnInfo(name = "area_type_id_map") val areaTypeId:String, //<<<<< see Area Type
#ColumnInfo(name = "area_is_active") val isActive:Boolean
)
#Entity(tableName = "SAMPLE")
data class SampleEntity(
#PrimaryKey #ColumnInfo(name = "sample_id") val id:String, //<<<<< unique name
#ColumnInfo(name = "sample_title") val title:String,
)
#Entity(tableName = "PACK")
data class PackEntity(
#PrimaryKey #ColumnInfo(name = "pack_id") val id:String, //<<<<< unique name
#ColumnInfo(name = "pack_title") val title:String, //<<<<< unique name
)
#Entity(
tableName = "UNIT",
foreignKeys = [
ForeignKey(
entity = SampleEntity::class,
parentColumns = ["sample_id"],
childColumns = ["sample_id_map"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = AreaEntity::class,
parentColumns = ["area_id"],
childColumns = ["area_id_map"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = PackEntity::class,
parentColumns = ["pack_id"],
childColumns = ["pack_id_map"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class UnitEntity(
#PrimaryKey val id:String,
#ColumnInfo(name = "sample_id_map") val sampleId : String,
#ColumnInfo(name = "area_id_map") val areaId:String,
#ColumnInfo(name = "pack_id_map") val packTypeId: String,
#ColumnInfo(name = "unit_is_active") val isActive:Boolean
)
#Entity(
tableName = "AREA_TYPE"
)
data class AreaTypeEntity(
#PrimaryKey #ColumnInfo(name = "area_type_id") val id:String, //<<<<< unique name
#ColumnInfo(name = "area_type_title") val title:String,
/* ???? should an area type have an area as a parent? potential issues if so */
/* commented out
#ColumnInfo(name = "area_type_title") val parentAreaId : String //<<<<< unique name
*/
)
data class AreaPOJO(
#Embedded val areaEntity : AreaEntity,
#Relation(
parentColumn = "area_type_id_map", //<<<<< changed accordingly
entityColumn = "area_type_id" //<<<<< changed accordingly
)
val areaTypeEntity : AreaTypeEntity
)
data class UnitPOJO (
#Embedded val unitEntity : UnitEntity,
#Relation (
entity = AreaEntity::class, //<<<<< ADDED
parentColumn = "area_id_map",
entityColumn = "area_id"
)
val areaWithAreaType : AreaPOJO,
#Relation (
parentColumn = "pack_id_map",
entityColumn = "pack_id"
)
val packEntity : PackEntity,
#Relation (
parentColumn = "sample_id_map",
entityColumn = "sample_id"
)
val sampleEntity : SampleEntity
)
data class AlternativeAreaPOJO (
#Embedded val areaEntity: AreaEntity,
#Embedded val areaTypeEntity: AreaTypeEntity
)
data class AlternativeUnitPOJO (
#Embedded val unitEntity: UnitEntity,
#Embedded val alternativeAreaPOJO: AlternativeAreaPOJO,
#Embedded val packEntity: PackEntity,
#Embedded val sampleEntity: SampleEntity
)
The #Dao annotated interface AllDao :-
#Dao
interface AllDAO {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(areaEntity: AreaEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(unitEntity: UnitEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(sampleEntity: SampleEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(packEntity: PackEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(areaTypeEntity: AreaTypeEntity)
#Transaction
#Query("SELECT * FROM UNIT")
fun getUnitsWithRelations(): List<UnitPOJO>
#Query("SELECT * FROM UNIT " +
"INNER JOIN AREA ON UNIT.area_id_map = AREA.area_id " +
"INNER JOIN AREA_TYPE ON AREA.area_type_id_map = AREA_TYPE.area_type_id " +
"INNER JOIN SAMPLE ON UNIT.sample_id_map = SAMPLE.sample_id " +
"INNER JOIN PACK ON UNIT.pack_id_map = PACK.pack_id")
fun getAlternativeUnitsWithRelations(): List<AlternativeUnitPOJO>
}
The #Database annotated class TheDatabase :-
#Database(entities = [
AreaEntity::class,
SampleEntity::class,
PackEntity::class,
UnitEntity::class,
AreaTypeEntity::class
],
version = 1,
exportSchema = false
)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDAO(): AllDAO
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(
context,
TheDatabase::class.java,
"the_database.db"
)
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
Note for convenience and brevity .allowMainThreadQueries has been utilised.
Code within an activity (designed to run just the once):-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDAO
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDAO()
val TAG = "DBINFO"
val p1 = PackEntity("P001","Pack1")
val p2 = PackEntity("P002","Pack2")
val p3 = PackEntity("P003","Pack3")
dao.insert(p1)
dao.insert(p2)
dao.insert(p3)
val s1 = SampleEntity("S001","Sample1")
val s2 = SampleEntity("S002","Sample2")
val s3 = SampleEntity("S003","Sample3")
dao.insert(s1)
dao.insert(s2)
dao.insert(s3)
val at1 = AreaTypeEntity("AT001","AreaType1")
val at2 = AreaTypeEntity("AT002","AreaType2")
val at3 = AreaTypeEntity("AT003","AreaType3",)
dao.insert(at1)
dao.insert(at2)
dao.insert(at3)
val a1 = AreaEntity("A001","Area1",at1.id,true)
val a2 = AreaEntity("A002","Area2",at2.id,false)
val a3 = AreaEntity("A003","Area3",at1.id,true)
dao.insert(a1)
dao.insert(a2)
dao.insert(a3)
dao.insert(UnitEntity("U001",s1.id,a1.id,p1.id,true))
dao.insert(UnitEntity("U002",s2.id,a2.id,p2.id, false))
dao.insert(UnitEntity("U003",s3.id,a3.id,p3.id,true))
dao.insert(UnitEntity("U004",s1.id,a2.id,p3.id,false))
dao.insert(UnitEntity("U005",s3.id,a2.id,p1.id, true))
for(uwr in dao.getUnitsWithRelations()) {
Log.d(TAG,
"Unit is ${uwr.unitEntity.id} " +
"Active = ${uwr.unitEntity.isActive} " +
"Sample is ${uwr.sampleEntity.title} " +
"Area is ${uwr.areaWithAreaType.areaEntity.title} " +
"AreaType is ${uwr.areaWithAreaType.areaTypeEntity.title}"
)
}
for (auwr in dao.getAlternativeUnitsWithRelations()) {
Log.d(TAG,
"Unit is ${auwr.unitEntity.id} " +
"Active is ${auwr.unitEntity.isActive} " +
"Sample is ${auwr.sampleEntity.title} " +
"Area is ${auwr.alternativeAreaPOJO.areaEntity.title} " +
"AreaType is ${auwr.alternativeAreaPOJO.areaTypeEntity.title}"
)
}
}
}
Last the resultant output from the log:-
2022-04-05 09:32:40.528 D/DBINFO: Unit is U001 Active = true Sample is Sample1 Area is Area1 AreaType is AreaType1
2022-04-05 09:32:40.528 D/DBINFO: Unit is U002 Active = false Sample is Sample2 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.529 D/DBINFO: Unit is U003 Active = true Sample is Sample3 Area is Area3 AreaType is AreaType1
2022-04-05 09:32:40.529 D/DBINFO: Unit is U004 Active = false Sample is Sample1 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.529 D/DBINFO: Unit is U005 Active = true Sample is Sample3 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.537 D/DBINFO: Unit is U001 Active is true Sample is Sample1 Area is Area1 AreaType is AreaType1
2022-04-05 09:32:40.537 D/DBINFO: Unit is U002 Active is false Sample is Sample2 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.537 D/DBINFO: Unit is U003 Active is true Sample is Sample3 Area is Area3 AreaType is AreaType1
2022-04-05 09:32:40.537 D/DBINFO: Unit is U004 Active is false Sample is Sample1 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.537 D/DBINFO: Unit is U005 Active is true Sample is Sample3 Area is Area2 AreaType is AreaType2
i.e. the results are the same for the 2 alternatives and of course the relationships are working as expected
Actually, I have solved it by this POJO :
data class UnitPOJO
(
#Embedded val unit: UnitEntity,
#Relation(
parentColumn = "sample_id",
entityColumn = "id"
)
val sampleEntity: SampleEntity,
#Relation(
parentColumn = "pack_type_id",
entityColumn = "id"
)
val pack: PackEntity,
#Relation(
parentColumn = "area_id",
entityColumn = "id",
entity = AreaEntity::class
)
val area: AreaPOJO
)
and AreaPOJO like this :
data class AreaPOJO(
#Embedded val areaEntity: AreaEntity,
#Relation(
parentColumn = "area_type_id",
entityColumn = "id"
)
val areaTypeEntity: AreaTypeEntity
)
But I will definitely consider your warnings #MikeT about naming the fields/column names when using hierarchical data. If something goes wrong or I get unexpected results, I will definitely use this approach to solve the problem.
I need to triple join my entities using #relation anotion in room, but I don't know how.
here is my summary of entities:
#Entity(tableName = "session_table")
data class Session(
#PrimaryKey(autoGenerate = true)
var sessionId: Long = 0L,
#ColumnInfo(name = "lesson_id")
var lessonId: Long
)
#Entity(tableName = "lessons_table")
data class Lesson(
#PrimaryKey(autoGenerate = true)
val lessonId: Long,
#ColumnInfo(name = "teacher_id")
var teacherId: Long = -1L
)
#Entity(tableName = "teacher_table")
data class Teacher(
#PrimaryKey(autoGenerate = true)
val teacherId: Long = 0L
)
I assume the answer would be something like this:
data class SessionWithLessonWithTeacher(
#Embedded
val session: Session,
#Relation(
parentColumn = "lesson_id",
entityColumn = "lessonId"
)
var lesson: Lesson,
#Relation(
parentColumn = "teacher_id", // this is the teacher id in lesson
entityColumn = "teacherId",
)
var teacher: Teacher
)
Your guess at what SessionWithlessonWithTeacher should be won't work because it is effectively saying get the teacher_id from the Session. There is no teacher_id in a Session.
To use #Relation's then you need to follow the hierarchy. A Session has a Lesson and a Lesson has a Teacher. So in Session you need to get a Lesson with a Teacher.
As such have (working up though the hierarchy) :-
data class LessonWithTeacher(
#Embedded
val lesson: Lesson,
#Relation(
entity = Teacher::class,
parentColumn = "teacher_id",
entityColumn = "teacherId")
val teacher: Teacher
)
and
data class SessionWithLessonWithTeacher(
#Embedded
val session: Session,
#Relation(
entity = Lesson::class, /* NOTE Lesson NOT LessonWithTeacher (not a table) */
parentColumn = "lesson_id",
entityColumn = "lessonId")
val lessonWithTeacher: LessonWithTeacher
)
You would use an #Dao such as :-
#Query("SELECT * FROM session_table")
#Transaction
abstract fun getSessionWithLessonWithTeacher(): List<SessionWithLessonWithTeacher>
Alternative Approach
The way that Room uses #Relation is that it initially only retrieves the Embedded object, it then retrieves all of the #Related objects via subsequent queries and hence why it warns(expects) #Transaction.
For your scenario, where there will be 1 lesson per session and 1 teacher per lesson, you can embed all three and have a single query (albeit it more complicated) that JOIN's the three tables.
So instead of (or as well as) SessionWithLessonWithTeacher you could have:-
data class SessionLessonTeacher(
#Embedded
val session: Session,
#Embedded
val lesson: Lesson,
#Embedded
val teacher: Teacher
)
note #Embedded can be a pain if column names aren't unique between the embedded objects
The equivalent query would/could be :-
#Query("SELECT * FROM session_table " +
"JOIN lessons_table ON lessons_table.lessonId = session_table.lesson_id " +
"JOIN teacher_table ON teacher_table.teacherId = lessons_table.teacher_id")
abstract fun getSessionLessonTeacher(): List<SessionLessonTeacher>
note that in your case the table.column could just be column as the column names are all unique.
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 am trying to create relation between tables where one of the tables references another one twice. Here is the code:
#Entity(tableName = "message",
foreignKeys = [
ForeignKey(entity = Contact::class,
parentColumns = ["id"],
childColumns = ["toContactId"],
onDelete = NO_ACTION
),
ForeignKey(entity = Contact::class,
parentColumns = ["id"],
childColumns = ["fromContactId"],
onDelete = NO_ACTION)
], indices = [Index("toContactId"), Index("fromContactId")]
)
data class Message(
#PrimaryKey var id: String,
var creationDate: Date,
var messageStatus: MessageStatus,
var toContactId: String,
var fromContactId: String,
var text: String
)
#Entity(tableName = "contact")
data class Contact(
#PrimaryKey val id: String,
val firstName: String,
val lastName: String,
val cellPhone: String,
val email: String
)
And here is how I have created "relation" class:
data class MessageRelations(#Embedded var message: Message,
#Embedded var toContact: Contact,
#Embedded var fromContact: Contact)
This approach results in
error: Multiple fields have the same columnName: id. Field names: message > id, toContact > id, fromContact > id.
I also tried to add prefixes to annotation #Embedded(prefix = "to_") and #Embedded(prefix = "from_"). But in this case Room can't find matches between fileds returned by the query and those in MessageRelations class.
I'll be grateful for any hint on how to solve this issue.
I beleieve that using #Relation instead of #Embed will resolve those issues as rather than including the fields/variables from the Entity it builds the object according to the relationship.
So you would have something like :-
data class MessageRelations(#Embedded var message: Message,
#Relation(parentColumn = "toContactId", entityColumn = "id") var toContact: Contact,
#Relation(parentColumn = "fromContactId", entityColumn = "id") var fromContact: Contact)
Note the above is in-principle code and has not been tested or run.