POJO within POJO, Entity-Relation problem - android

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.

Related

How to insert and query nested entities in Room database received from API that contains one to many relationships

I have an API that returns a DTO containing entities with the one-to-many relationships filled in a list.
I'm struggling to figure out how work with that data in Room.
I need to map this DTO to the corresponding Entities. I then need to insert them in the database. And later on, I want to query them and retrieve a BoardEntity with a list of its corresponding BoardChildEntities
fun getBoards() = networkBoundResource(query = {
// I need to query all boards from Room and add to that its corresponding children in a list
},
fetch = {
api.getBoards()
},
saveFetchResult = { dtos ->
// how can I save all the data in the DTOs in their corresponding tables
// without writing a lot of nested loops
})
The DTO returned from the api:
data class BoardDto(
val id: Int,
val name: String,
val boardChildren: List<BoardChildDto>,
) {
data class BoardChildDto(
val id: Int,
val boardId: Int, // foreign key
val name: String,
val boardElements: List<BoardElementDto>,
) {
data class BoardElementDto(
val id: Int,
val boardChildId: Int, // foreign key
val name: String,
val type: String,
val hyperlinks: List<BoardElementHyperlinkDto>,
) {
data class BoardElementHyperlinkDto(
val id: Int,
val boardElementId: Int, // foreign key
val name: String,
)
}
}
}
The Room entities:
#Entity
data class BoardEntity(
#PrimaryKey(autoGenerate = false) val id: Int,
val icon: String,
val name: String,
)
#Entity(
foreignKeys = [ForeignKey(
entity = BoardEntity::class,
parentColumns = ["id"],
childColumns = ["boardId"],
onDelete = ForeignKey.CASCADE
)]
)
data class BoardChildEntity(
#PrimaryKey(autoGenerate = false) val id: Int,
val boardId: Int,
val name: String,
)
#Entity(
foreignKeys = [ForeignKey(
entity = BoardChildEntity::class,
parentColumns = ["id"],
childColumns = ["boardChildId"],
onDelete = ForeignKey.CASCADE
)]
)
data class BoardElementEntity(
#PrimaryKey(autoGenerate = false) val id: Int,
val boardChildId: Int,
val name: String,
)
#Entity(
foreignKeys = [ForeignKey(
entity = BoardElementEntity::class,
parentColumns = ["id"],
childColumns = ["boardElementId"],
onDelete = ForeignKey.CASCADE
)]
)
data class BoardElementHyperlinkEntity(
#PrimaryKey(autoGenerate = false) val id: Int,
val boardElementId: Int,
val name: String,
)
The mappers from DTOs to Room entities
fun BoardDto.toEntityModel(): BoardEntity {
return BoardEntity(
id = id,
name = name,
)
}
fun BoardChildDto.toEntityModel(): BoardChildEntity {
return BoardChildEntity(
id = id,
boardId = boardId,
name = name,
)
}
fun BoardElementDto.toEntityModel(): BoardElementEntity {
return BoardElementEntity(
id = id,
boardChildId = boardChildId,
name = name,
)
}
fun BoardElementHyperlinkDto.toEntityModel(): BoardElementHyperlinkEntity {
return BoardElementHyperlinkEntity(
id = id,
boardElementId = boardElementId,
name = name,
)
}
// how can I save all the data in the DTOs in their corresponding tables
// without writing a lot of nested loops
Depends upon what you call lots, some cannot really be avoided.
Here's is an example of how this can be achieved (from an #Dao annotated interface):-
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(boardEntity: BoardEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(boardChildEntity: BoardChildEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(boardElementEntity: BoardElementEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(boardElementHyperlinkEntity: BoardElementHyperlinkEntity): Long
#Transaction
#Query("")
fun insertDTO(dto: BoardDto, icon: String): Int {
val boardId = insert(BoardEntity( id = dto.id,name = dto.name, icon = icon))
if (boardId < 0) return TheDatabase.BOARD_NOT_INSERTED
for (bc in dto.boardChildren) {
val boardChildId = insert(BoardChildEntity(id = bc.id, boardId = boardId.toInt(), name = bc.name))
if (boardChildId < 0) return TheDatabase.BOARDCHILD_NOT_INSERTED
for (be in bc.boardElements) {
val boardElementId = insert(BoardElementEntity(id = be.id, boardChildId = boardChildId.toInt(), name = be.name))
if (boardElementId < 0) return TheDatabase.BOARDELEMENT_NOT_INSERTED
for (beh in be.hyperlinks) {
val boardElementHyperlinkId = insert(BoardElementHyperlinkEntity(id = beh.id, boardElementId = boardElementId.toInt(), beh.name))
if (boardElementHyperlinkId < 0) return TheDatabase.BOARDHYPERLINK_NOT_INESRTED
}
}
}
return boardId.toInt()
}
So initially the 4 standard convenience inserts
Then a function with a body that 3 nested loops (noting that for the sake of less hassle demonstrating that the foreign keys are cascaded and overwrite the hard code values (see demo and results)). Of the actual values if correct could be used (if not then you would encounter foreign key conflicts). In a similar vein if an insert is IGNORED at whatever level then an immediate return is made, an Int is returned according to come constants (-99 through to -96 inclusive) (this behaviour can easily be removed).
And later on, I want to query them and retrieve a BoardEntity with a list of its corresponding BoardChildEntities
and I assume the with the child BoardElements and also with the HyperLink children (down through all the hierarchy).
For this you use a hierarchy of POJO's each getting the parent and the children. e.g. the following POJO's with the parent having the #Embed annotation and the list of children having the #Relation annotation.
data class BoardElementEntityWithHyperlinkEntity(
#Embedded
val boardElementEntity: BoardElementEntity,
#Relation( entity = BoardElementHyperlinkEntity::class, parentColumn = "id", entityColumn = "boardElementId")
val boardElementHyperlinkEntityList: List<BoardElementHyperlinkEntity>
)
data class BoardChildEntityWithElementEntity(
#Embedded
val boardChildEntity: BoardChildEntity,
#Relation( entity = BoardElementEntity::class, parentColumn = "id", entityColumn = "boardChildId")
val boardElementEntityList: List<BoardElementEntityWithHyperlinkEntity>
)
data class BoardEntityWithBoardChildList(
#Embedded
val boardEntity: BoardEntity,
#Relation(entity = BoardChildEntity::class, parentColumn = "id", entityColumn = "boardId")
val BoardChildEntityList: List<BoardChildEntityWithElementEntity>
)
Note in the #Relation how the entity= is the #Entity annotated class not the class of the field.
The POJO's are listed in reverse order as experience has shown that it is easier to create them from the bottom up.
DEMO
Here's a working demo that when run takes adds the data from a BoardDTO and then extracts it (albeit that some references, such as boardId, are replaced).
First the entire code for the Database Stuff and also your BoardDTo (your mappers haven't been used):-
data class BoardDto(
val id: Int,
val name: String,
val boardChildren: List<BoardChildDto>,
) {
data class BoardChildDto(
val id: Int,
val boardId: Int, // foreign key
val name: String,
val boardElements: List<BoardElementDto>,
) {
data class BoardElementDto(
val id: Int,
val boardChildId: Int, // foreign key
val name: String,
val type: String,
val hyperlinks: List<BoardElementHyperlinkDto>,
) {
data class BoardElementHyperlinkDto(
val id: Int,
val boardElementId: Int, // foreign key
val name: String,
)
}
}
}
#Entity
data class BoardEntity(
#PrimaryKey(autoGenerate = false) val id: Int,
val icon: String,
val name: String,
)
#Entity(
foreignKeys = [ForeignKey(
entity = BoardEntity::class,
parentColumns = ["id"],
childColumns = ["boardId"],
onDelete = ForeignKey.CASCADE
)]
)
data class BoardChildEntity(
#PrimaryKey(autoGenerate = false) val id: Int,
val boardId: Int,
val name: String,
)
data class BoardEntityWithBoardChildList(
#Embedded
val boardEntity: BoardEntity,
#Relation(entity = BoardChildEntity::class, parentColumn = "id", entityColumn = "boardId")
val BoardChildEntityList: List<BoardChildEntityWithElementEntity>
)
#Entity(
foreignKeys = [ForeignKey(
entity = BoardChildEntity::class,
parentColumns = ["id"],
childColumns = ["boardChildId"],
onDelete = ForeignKey.CASCADE
)]
)
data class BoardElementEntity(
#PrimaryKey(autoGenerate = false) val id: Int,
val boardChildId: Int,
val name: String,
)
data class BoardChildEntityWithElementEntity(
#Embedded
val boardChildEntity: BoardChildEntity,
#Relation( entity = BoardElementEntity::class, parentColumn = "id", entityColumn = "boardChildId")
val boardElementEntityList: List<BoardElementEntityWithHyperlinkEntity>
)
#Entity(
foreignKeys = [ForeignKey(
entity = BoardElementEntity::class,
parentColumns = ["id"],
childColumns = ["boardElementId"],
onDelete = ForeignKey.CASCADE
)]
)
data class BoardElementHyperlinkEntity(
#PrimaryKey(autoGenerate = false) val id: Int,
val boardElementId: Int,
val name: String,
)
data class BoardElementEntityWithHyperlinkEntity(
#Embedded
val boardElementEntity: BoardElementEntity,
#Relation( entity = BoardElementHyperlinkEntity::class, parentColumn = "id", entityColumn = "boardElementId")
val boardElementHyperlinkEntityList: List<BoardElementHyperlinkEntity>
)
#Dao
interface TheDAOs {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(boardEntity: BoardEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(boardChildEntity: BoardChildEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(boardElementEntity: BoardElementEntity): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(boardElementHyperlinkEntity: BoardElementHyperlinkEntity): Long
#Transaction
#Query("")
fun insertDTO(dto: BoardDto, icon: String): Int {
val boardId = insert(BoardEntity( id = dto.id,name = dto.name, icon = icon))
if (boardId < 0) return TheDatabase.BOARD_NOT_INSERTED
for (bc in dto.boardChildren) {
val boardChildId = insert(BoardChildEntity(id = bc.id, boardId = boardId.toInt(), name = bc.name))
if (boardChildId < 0) return TheDatabase.BOARDCHILD_NOT_INSERTED
for (be in bc.boardElements) {
val boardElementId = insert(BoardElementEntity(id = be.id, boardChildId = boardChildId.toInt(), name = be.name))
if (boardElementId < 0) return TheDatabase.BOARDELEMENT_NOT_INSERTED
for (beh in be.hyperlinks) {
val boardElementHyperlinkId = insert(BoardElementHyperlinkEntity(id = beh.id, boardElementId = boardElementId.toInt(), beh.name))
if (boardElementHyperlinkId < 0) return TheDatabase.BOARDHYPERLINK_NOT_INESRTED
}
}
}
return boardId.toInt()
}
#Transaction
#Query("SELECT * FROM boardentity")
fun getAllBoardsWithFamily(): List<BoardEntityWithBoardChildList>
}
#Database(
entities = [
BoardElementHyperlinkEntity::class,
BoardElementEntity::class,
BoardChildEntity::class,
BoardEntity::class
],
exportSchema = false,
version = 1
)
abstract class TheDatabase: RoomDatabase() {
abstract fun getTheDAOs(): TheDAOs
companion object {
const val BOARD_NOT_INSERTED = -99
const val BOARDCHILD_NOT_INSERTED = -98
const val BOARDELEMENT_NOT_INSERTED = -97
const val BOARDHYPERLINK_NOT_INESRTED = -96
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
}
}
}
To actually test then the following activtiy code (MainActivity):-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: TheDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getTheDAOs()
val beh = BoardDto.BoardChildDto.BoardElementDto.BoardElementHyperlinkDto(11100,9999,"BEH0001")
val behlist1 = listOf(
beh,
BoardDto.BoardChildDto.BoardElementDto.BoardElementHyperlinkDto(22200,9999,"BEH0002"),
BoardDto.BoardChildDto.BoardElementDto.BoardElementHyperlinkDto(33300,9999,"BEH0003")
)
val behlist2 = listOf(
BoardDto.BoardChildDto.BoardElementDto.BoardElementHyperlinkDto(44400,9999,"BEH0004"),
BoardDto.BoardChildDto.BoardElementDto.BoardElementHyperlinkDto(55500,9999,"BEH0005")
)
val belist1 = listOf(
BoardDto.BoardChildDto.BoardElementDto(id = 1100, boardChildId = 999, name = "BE0001", type = "A", hyperlinks = behlist1),
BoardDto.BoardChildDto.BoardElementDto(id= 2200, boardChildId = 999, name = "BE0002", type = "B", hyperlinks = behlist2)
)
val bclist = listOf(
BoardDto.BoardChildDto(id = 110, boardId = 99, name = "BC0001", boardElements = belist1 ),
BoardDto.BoardChildDto(id = 220, boardId = 99, name = "BC0002", boardElements = belist1 ),
)
dao.insertDTO(BoardDto(id = 11, boardChildren = bclist, name = "B00001"), icon = "unsure")
for (b in dao.getAllBoardsWithFamily()) {
Log.d(TAG,"Board Name is ${b.boardEntity.name} ICON is ${b.boardEntity.icon} ID is ${b.boardEntity.id}. Board has ${b.BoardChildEntityList.size} BoardChildren. They are:-")
for (bc in b.BoardChildEntityList) {
Log.d(TAG,"\tBC Name is ${bc.boardChildEntity.name} ID is ${bc.boardChildEntity.id} Mapped To Board ${bc.boardChildEntity.boardId}. ${bc.boardElementEntityList.size} elements:-")
for(be in bc.boardElementEntityList) {
Log.d(TAG,"\t\tBE Name is ${be.boardElementEntity.name} ${be.boardElementHyperlinkEntityList.size} elemets:-")
for (beh in be.boardElementHyperlinkEntityList) {
Log.wtf(TAG,"\t\t\tBEH name is ${beh.name} ID is ${beh.id} maps to ${beh.boardElementId}")
}
}
}
}
}
companion object {
const val TAG = "DBINFO"
}
}
Note the code is only intended to run the once
When run then the result output to the log is:-
D/DBINFO: Board Name is B00001 ICON is unsure ID is 11. Board has 2 BoardChildren. They are:-
D/DBINFO: BC Name is BC0001 ID is 110 Mapped To Board 11. 2 elements:-
D/DBINFO: BE Name is BE0001 3 elemets:-
D/DBINFO: BEH name is BEH0001 ID is 11100 maps to 1100
D/DBINFO: BEH name is BEH0002 ID is 22200 maps to 1100
D/DBINFO: BEH name is BEH0003 ID is 33300 maps to 1100
D/DBINFO: BE Name is BE0002 2 elemets:-
D/DBINFO: BEH name is BEH0004 ID is 44400 maps to 2200
D/DBINFO: BEH name is BEH0005 ID is 55500 maps to 2200
D/DBINFO: BC Name is BC0002 ID is 220 Mapped To Board 11. 0 elements:-
note BC0002 will have no children as the list was the same as for BC0001 and thus the duplicates would have been skipped.
The database via App inspection:-

How to Query in a many to many relationship Room database?

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

room many to many error : columns returned by the query does not have the fields

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.

Listing All data Based on foreign keys android room

I've googled my question and i can't find the answer yet.
I want to get all the meals that is in favorite, below is the code and what i have tried.
The result is that i only get a single Meal, even though there are 3 data in dbfavoritemeal.
The expected result is that i will get all the meals based on all the mealid in dbfavoritemeal.
Please guide me
I have a meal class
#Entity
data class DbMeal(
#PrimaryKey val id: Long,
val name: String,
val thumbnailUrl: String,
val category: String,
val instructions: String = "",
) {
And then i have favorite class
#Entity(
foreignKeys = [
ForeignKey(
entity = DbMeal::class,
parentColumns = ["id"],
childColumns = ["mealId"],
onDelete = ForeignKey.CASCADE
)],
indices = [Index(
value = ["mealId"],
unique = true
)]
)
data class DbFavoriteMeal(
#PrimaryKey
val mealId: Long
)
What i've tried is in DAO
#Query("select * from dbMeal where id = (select mealId from dbfavoritemeal)")
suspend fun getAllFavoriteDbMeal(): List<DbMeal>
You can change your DAO like
#Query("select * from dbMeal where id in (select mealId from dbfavoritemeal)")
suspend fun getAllFavoriteDbMeal(): List<DbMeal>
or you can add isFavorite parameter to your Entity.
#Entity
data class DbMeal(
#PrimaryKey val id: Long,
val name: String,
val thumbnailUrl: String,
val category: String,
val instructions: String = "",
val isFavorite: Boolean = false,
)
And your DAO should look like
#Query("select * from dbMeal where isFavorite = 1")
suspend fun getAllFavoriteDbMeal(): List<DbMeal>

Join 2 foreign key relationships into one object in query

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.

Categories

Resources