I'm attempting to create a one-to-many relationship where each instance of user corresponds to zero or more instances of role entity.
Problem: UserRoleJunction is having issues in the #Relation for both parentColumn (user) and entityColumn (role)
I didn't reach to DAO implementation yet because of the error.
user
+----+----------+----------+
| id | username | name |
+----+----------+----------+
| 1 | johndoe | John Doe |
| 2 | janedoe | Jane Doe |
+----+----------+----------+
data class
#Entity(tableName = "user")
data class User(
#PrimaryKey (autoGenerate = true)
var id: Long,
var username: String? = null,
var name: String? = null)
role
+----+----------+
| id | name |
+----+----------+
| 1 | Sales |
| 2 | Shipping |
| 3 | Accounts |
+----+----------+
data class
#Entity(tableName = "role")
data class Role(
#PrimaryKey(autoGenerate = true)
var id: Long,
var name: String? = null)
user_role Join or CrossRef Table
+---------+---------+
| user_id | role_id |
+---------+---------+
| 1 | 1 |
| 1 | 3 |
| 2 | 1 |
+---------+---------+
data class
#Entity(tableName = "user_role", primaryKeys = ["user_id", "role_id"])
data class UserRoleJoin( // CrossRef
#ColumnInfo(name = "user_id") var userId: Int,
#ColumnInfo(name = "role_id") var roleId: Int)
Junction data class
data class UserRoleJunction (
#Embedded var user: User,
#Relation(
parentColumn = "id", // User -> error: see below
entityColumn = "id", // Role -> error: see below
associateBy = Junction(UserRoleJoin::class)
)
var roles: List<Role>
)
Error 1 UserRoleJunction
parentColumn
error: Cannot find the parent entity referencing column id in the junction UserRoleJoin. Options: user_id, role_id
entityColumn
error: Cannot find the child entity referencing column id in the junction UserRoleJoin. Options: user_id, role_id
I did try substituting user_id and role_id as per the error messages but it keeps throwing similar errors like above.
Error 2 UserRoleJunction
Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type).
When using a association/junction there are 4 columns involved.
Two parents and two children. The parentColumn and entityColumn at the #Relation level define the columns in the #Relation (Role) and the #Embedded (User) tables.
The Junction is used to specify the respective columns in the junction table itself, these have been omitted and are the cause of the errors.
So you should be using:-
Junction(UserRoleJoin::class, parentColumn = "user_id", entityColumn = "role_id")
Related
I have many-to-many relation on two entities: Student and Teacher. They common table is course. Every student can have a course with a teacher. This is table schemas I'm filling them with initial test values:
//Fill student table
db.execSQL("INSERT OR IGNORE INTO student_table (studentId ,name , age,cityId ) VALUES (1,'Mahdi',39 ,3)".trimIndent())
//Fill teacher table
db.execSQL("INSERT OR IGNORE INTO teacher_table (teacherId, name , grade ) VALUES (1,'Zahra',99 )".trimIndent())
db.execSQL("INSERT OR IGNORE INTO teacher_table (teacherId, name , grade ) VALUES (2,'Shaby',120 )".trimIndent())
//Fill course
db.execSQL("INSERT OR IGNORE INTO course_table (courseId , studentId,teacherId ) VALUES (1,1,1 )".trimIndent())
db.execSQL("INSERT OR IGNORE INTO course_table (courseId , studentId,teacherId ) VALUES (5,1,2 )".trimIndent())
...
I wan to have a result that show students that connected to teachers by courses like this:
data class StudentAndTeachers(
val student: Student,
val teachers: List<Teacher>
)
So I expected to see teacher 1, 2 in relation with student 1.
In sql I did a join like this:
SELECT * FROM student_table LEFT join course_table on course_table.studentId = student_table.studentId LEFT JOIN teacher_table on course_table.teacherId = teacher_table.teacherId group by student_table.studentId
But result wont fill list of teachers and only contain one teacher per student like bellow, but in my table I should see two teachers ( teacher id 1,2 ) for student ( studentId = 1 )
So how I can make result of join create list of my all relation ?
User1 (listOf(teacher1, teacher2))
It is hard to imagine what the schema is. But it would appear that you do not have a many to many relationship as you would accomplish this with what is known by terms such as mapping table, reference table, associative table .... (all basically the same thing)
Such a table typically has two columns one that references/maps one of the tables (e.g. student) and the other column references/maps to the other table such as the course.
Anyway here is an example of what you appear to be trying to achieve. It has a table for Students, a table for Teachers and a table for Courses. The course table includes a column that references/maps the teacher (assuming one teacher per course). Lastly there is the mapping table that caters for the many-many relationship that allows a course to have many students and for a student to be in many courses.
The Entities are :-
Teacher
#Entity(tableName = "teacher_table")
data class Teacher(
#PrimaryKey
var teacherId: Long? = null,
var teacherDetails: String
)
Student
#Entity(tableName = "student_table")
data class Student(
#PrimaryKey
var studentId: Long? = null,
var studentDetails: String
)
Course
#Entity(tableName = "course_table",
/* Foreign Keys optional but enforces referential integrity */
foreignKeys = [
ForeignKey(
entity = Teacher::class,
parentColumns = ["teacherId"],
childColumns = ["teacherIdMap"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class Course(
#PrimaryKey
var courseId: Long? = null,
var courseDetails: String,
#ColumnInfo(index = true)
var teacherIdMap: Long /* if only one teacher for the course */
)
Last the mapping table Entity StudentCourseMap
#Entity(tableName = "student_course_map_table",
primaryKeys = ["studentIdMap","courseIdMap"],
foreignKeys = [
ForeignKey(entity = Student::class,
parentColumns = ["studentId"],
childColumns = ["studentIdMap"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey( entity = Course::class,
parentColumns = ["courseId"],
childColumns = ["courseIdMap"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class StudentCourseMap(
var studentIdMap: Long,
#ColumnInfo(index = true)
var courseIdMap: Long
)
To support the above and get a Course, with the Teacher and with All the Students in the course there is a POJO class CourseWithTeacherAndStudents
data class CourseWithTeacherAndStudents(
#Embedded
var course: Course,
#Embedded
var teacher: Teacher,
#Relation(
entity = Student::class,
parentColumn = "courseId",
entityColumn = "studentId",
associateBy = Junction(
StudentCourseMap::class,
parentColumn = "courseIdMap",
entityColumn = "studentIdMap"
)
)
var studentList: List<Student>
)
This is similar to the class in your question (but also includes the course details) and is along the lines of what you want to retrieve from the database.
data class CourseWithTeacherAndStudents(
#Embedded
var course: Course,
#Embedded
var teacher: Teacher,
#Relation(
entity = Student::class,
parentColumn = "courseId",
entityColumn = "studentId",
associateBy = Junction(
StudentCourseMap::class,
parentColumn = "courseIdMap",
entityColumn = "studentIdMap"
)
)
var studentList: List<Student>
)
To utilise the above the AllDAO is an #Dao annotated abstract class (can be an interface but if you want to process data from your own JOINS and abstract class can have functions with bodies ).
#Dao
abstract class AllDAO {
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(teacher: Teacher): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(student: Student): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(course: Course): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(courseMap: StudentCourseMap): Long
#Query("SELECT courseId FROM course_table WHERE coursedetails=:courseDetails ")
abstract fun getCourseIdByCourseDetails(courseDetails: String): Long
#Transaction
#Query("SELECT * FROM course_table JOIN teacher_table ON course_table.teacherIdMap = teacher_table.teacherId")
abstract fun getAllCoursesWithTeacherAndStudents(): List<CourseWithTeacherAndStudents>
}
Putting it all together with a working example
with a pretty standard #Database annotated class (in this case for brevity and convenience .allowMainThreadQueries has be used)
is the following in an Activity
:-
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 teacherId01 = dao.insert(Teacher(teacherDetails = "teacher1"))
val teacherId02 = dao.insert(Teacher(teacherDetails = "teacher2"))
val teacherId03 = dao.insert(Teacher(teacherDetails = "teacher3"))
val teacherId04 = dao.insert(Teacher(teacherDetails = "teacher4")) // not used
val courseId001 = dao.insert(Course(courseDetails = "Course1", teacherIdMap = teacherId01))
val courseId002 = dao.insert(Course(courseDetails = "Course2", teacherIdMap = teacherId01))
val courseId003 = dao.insert(Course(courseDetails = "Course3", teacherIdMap = teacherId02))
val courseId004 = dao.insert(Course(courseDetails = "Course4", teacherIdMap = teacherId03))
dao.insert(Course(courseDetails = "Course5", teacherIdMap = dao.insert(Teacher(teacherDetails = "teacher5"))))
val studentID01 = dao.insert(Student(studentDetails = "student1"))
val studentID02 = dao.insert(Student(studentDetails = "student2"))
val studentID03 = dao.insert(Student(studentDetails = "student3"))
val studentID04 = dao.insert(Student(studentDetails = "student4"))
val studentID05 = dao.insert(Student(studentDetails = "student5"))
val studentID06 = dao.insert(Student(studentDetails = "student6"))
val studentID07 = dao.insert(Student(studentDetails = "student7"))
val studentID08 = dao.insert(Student(studentDetails = "student8"))
dao.insert(StudentCourseMap(studentID01,courseId001))
dao.insert(StudentCourseMap(studentID01,courseId003))
dao.insert(StudentCourseMap(studentID01,dao.getCourseIdByCourseDetails("Course5")))
dao.insert(StudentCourseMap(studentID02,courseId002))
dao.insert(StudentCourseMap(studentID02,courseId004))
dao.insert(StudentCourseMap(studentID03,courseId001))
dao.insert(StudentCourseMap(studentID04,courseId002))
dao.insert(StudentCourseMap(studentID05,courseId003))
dao.insert(StudentCourseMap(studentID06,courseId004))
dao.insert(StudentCourseMap(studentID07,dao.getCourseIdByCourseDetails("Course2")))
dao.insert(StudentCourseMap(studentID08,dao.getCourseIdByCourseDetails("Course5")))
for(cwtas: CourseWithTeacherAndStudents in dao.getAllCoursesWithTeacherAndStudents()) {
Log.d("DBINFO","Course is ${cwtas.course.courseDetails}. Teacher is ${cwtas.teacher.teacherDetails}. There are ${cwtas.studentList.size} students. they are:-")
for (s: Student in cwtas.studentList) {
Log.d("DBINFO","\tStudent Details are ${s.studentDetails} id is ${s.studentId}")
}
}
}
}
The result output to the log when running the above (just once) is :-
2022-03-23 19:01:57.337 D/DBINFO: Course is Course1. Teacher is teacher1. There are 2 students. they are:-
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student1 id is 1
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student3 id is 3
2022-03-23 19:01:57.337 D/DBINFO: Course is Course2. Teacher is teacher1. There are 3 students. they are:-
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student2 id is 2
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student4 id is 4
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student7 id is 7
2022-03-23 19:01:57.337 D/DBINFO: Course is Course3. Teacher is teacher2. There are 2 students. they are:-
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student1 id is 1
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student5 id is 5
2022-03-23 19:01:57.337 D/DBINFO: Course is Course4. Teacher is teacher3. There are 2 students. they are:-
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student2 id is 2
2022-03-23 19:01:57.337 D/DBINFO: Student Details are student6 id is 6
2022-03-23 19:01:57.338 D/DBINFO: Course is Course5. Teacher is teacher5. There are 2 students. they are:-
2022-03-23 19:01:57.338 D/DBINFO: Student Details are student1 id is 1
2022-03-23 19:01:57.338 D/DBINFO: Student Details are student8 id is 8
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 am trying to achieve the following
I have the following Entities
#Entity(tableName = "workspace_table")
data class WorkSpace(
#PrimaryKey
val workSpaceId:Long,
.....
)
#Entity(tableName = "widget_table")
data class Widget(
val widgetId:Long,
.......
)
#Entity(tableName = "feed_table")
data class Feed(
val feedId:Long,
.......
)
What I want from the 3 table is the below
POJO
data class MergedData(
#Embedded workSpace:WorkSpace,
#Embedded widget:List<Widget>,
#Embedded feeds:List<Feed>,
)
The relationship is like this
workSpaceId|widgetId|feedId|
1 | 1 | 2
1 | 1 | 1 |
2 | 1 | 2
2 | 2 | 1
Basically there is a many to many relation ship between workspace and widgets and widgets and feeds
They should come together when all of the three tables are participating
I went through the guide
https://developer.android.com/training/data-storage/room/relationships
and tried Mapping two way between widget and workspace and feed and widget
however those I am not even been able to build with that
I tried one to many with Workspace and Widget and Many to Many with Widget and Feeds
then I am getting feeds in my workspace for widgets which I don't want .
I am really confused at this point any nudge in the correct direction will greatly appreciated
Update
With Mike's Answer below I am getting this
2021-04-04 12:16:06.097 10237-10291/com.example.datacreation D/MainActivty: meta data [IntersectionWithWorkSpaceWidgetFeed(workSpace=WorkSpace(workSpaceId=2,
associatedUserId=test, workSpaceName=Demo),
widget=WidgetMetaData(widgetId=11, widgetName=Widget1, backgroundColor=None, widgetType=Normal, dataUrl=www),
feed=Feed(feedId=2, feedName=Feed2, count=0, asyncInterval=1234)),
IntersectionWithWorkSpaceWidgetFeed(workSpace=WorkSpace(workSpaceId=2, associatedUserId=test,
workSpaceName=Demo),
widget=WidgetMetaData(widgetId=12, widgetName=Widget2, backgroundColor=None, widgetType=normal, dataUrl=www),
feed=Feed(feedId=1, feedName=Feed1, count=2, asyncInterval=1234)),
IntersectionWithWorkSpaceWidgetFeed(workSpace=WorkSpace(workSpaceId=2, associatedUserId=igvuser, workSpaceName=Demo),
widget=WidgetMetaData(widgetId=13, widgetName=Widget3, backgroundColor=None, widgetType=normal, dataUrl=www),
feed=Feed(feedId=2, feedName=Feed2, count=0, asyncInterval=1234))]
Near enough to my original MergedData POJO. Thanks Mike.
I believe that you want a 3 way mapping table. Each row consisting of 3 columns, the WorkSpaceId, the WidgetId and the FeedId with the Primary Key composed of all 3 columns.
Assuming this here is a working example:-
The 3 base entities:
WorkSpace
#Entity(
tableName = "workspace_table",
)
data class WorkSpace(
#PrimaryKey
val workSpaceId:Long,
val workPlaceName: String
)
Widget
#Entity(
tableName = "widget_table",
)
data class Widget(
#PrimaryKey
val widgetId:Long,
val widgetName: String
)
Feed
#Entity(tableName = "feed_table")
data class Feed(
#PrimaryKey
val feedId:Long,
val feedName: String
)
The NEW mapping table WorkSpaceWidgetFeedIntersectionMap
#Entity(
tableName = "workspace_widget_feed_mapping_table",
foreignKeys = [
ForeignKey(
entity = WorkSpace::class,
parentColumns = ["workSpaceId"],
childColumns = ["workSpaceId_map"]
),
ForeignKey(
entity = Widget::class,
parentColumns = ["widgetId"],
childColumns = ["widgetId_map"]
),
ForeignKey(
entity = Feed::class,
parentColumns = ["feedId"],
childColumns = ["feedId_map"]
)
],
primaryKeys = ["workSpaceId_map","widgetId_map","feedId_map"],
)
data class WorkSpaceWidgetFeedIntersectionMap(
#NonNull
val workSpaceId_map: Long,
#NonNull
val widgetId_map: Long,
#NonNull
val feedId_map: Long
)
Foreign Keys are optional
The Dao's AllDao
#Dao
interface AllDao {
#Insert
fun insertWorkSpace(workSpace: WorkSpace): Long
#Insert
fun insertWidget(widget: Widget): Long
#Insert
fun insertFeed(feed: Feed): Long
#Insert
fun insertWorkSpaceWidgetFeedMap(workSpaceWidgetFeedIntersectionMap: WorkSpaceWidgetFeedIntersectionMap): Long
#Query("DELETE FROM workspace_table")
fun clearWorkSpaceTable(): Int
#Query("DELETE FROM widget_table")
fun clearWidgetTable(): Int
#Query("DELETE FROM feed_table")
fun clearFeedTable(): Int
#Query("DELETE FROM workspace_widget_feed_mapping_table")
fun clearWorkSpaceWidgetFeedMap(): Int
#Query("SELECT * FROM workspace_widget_feed_mapping_table")
fun getWorkSpaceWidgetFeedIntersections(): List<WorkSpaceWidgetFeedIntersectionMap>
}
The Database MyDatabase
#Database(entities = [WorkSpace::class,Widget::class,Feed::class,WorkSpaceWidgetFeedIntersectionMap::class],version = 1)
abstract class MyDatabase: RoomDatabase() {
abstract fun getAllDoa(): AllDao
}
And finally an Activity MainActivity to test the basic functionality
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val db = Room.databaseBuilder(applicationContext,MyDatabase::class.java,"mydb")
.allowMainThreadQueries()
.build()
val dao = db.getAllDoa()
clearAllTables(dao)
dao.insertWorkSpace(WorkSpace(1,"WorkSpace 1"))
dao.insertWorkSpace( WorkSpace(2,"WorkSpace 2"))
dao.insertWidget(Widget(1,"Widget 1"))
dao.insertWidget(Widget(2,"Widget 2"))
dao.insertFeed(Feed(1,"Feed 1"))
dao.insertFeed( Feed(2,"Feed 2"))
dao.insertWorkSpaceWidgetFeedMap(WorkSpaceWidgetFeedIntersectionMap(1,1,2))
dao.insertWorkSpaceWidgetFeedMap(WorkSpaceWidgetFeedIntersectionMap(1,1,1))
dao.insertWorkSpaceWidgetFeedMap(WorkSpaceWidgetFeedIntersectionMap(2,1,2))
dao.insertWorkSpaceWidgetFeedMap(WorkSpaceWidgetFeedIntersectionMap(2,2,1))
val wwfiList = dao.getWorkSpaceWidgetFeedIntersections()
for(cwwfi: WorkSpaceWidgetFeedIntersectionMap in wwfiList) {
Log.d("WWFIINFO","WorkSpaceID = " + cwwfi.workSpaceId_map + " WidgetID = " + cwwfi.widgetId_map + " FeedID = " + cwwfi.feedId_map)
}
}
private fun clearAllTables(dao: AllDao) {
dao.clearWorkSpaceWidgetFeedMap()
dao.clearFeedTable()
dao.clearWidgetTable()
dao.clearWorkSpaceTable()
}
}
gets the built the database (allowing to be run on main thread for convenience and brevity)
gets the dao
clears all the tables (makes test rerunnable)
adds 2 WorkSpaces, 2 Widgets and 2 Feeds
adds the intersection map entries
extracts and logs the intersections
Result
Running the above produces :-
2021-04-04 08:31:02.942 D/WWFIINFO: WorkSpaceID = 1 WidgetID = 1 FeedID = 2
2021-04-04 08:31:02.942 D/WWFIINFO: WorkSpaceID = 1 WidgetID = 1 FeedID = 1
2021-04-04 08:31:02.942 D/WWFIINFO: WorkSpaceID = 2 WidgetID = 1 FeedID = 2
2021-04-04 08:31:02.943 D/WWFIINFO: WorkSpaceID = 2 WidgetID = 2 FeedID = 1
You could easily then get the respective WorkSpace, Wdiget and Feed from the retrieved WorkSpaceWidgetFeedIntersectionMap.
see addtional
Additional
Now to get your MergedData (equivalent) then consider the following additions to the above
New data class IntersectionWithWorkSpaceWidgetFeed
:-
class IntersectionWithWorkSpaceWidgetFeed(
#Embedded
val workSpace: WorkSpace,
#Embedded
val widget: Widget,
#Embedded
val feed: Feed
)
An extra Dao function getWorkSpaceWidgetAndFeedFromIntersectionMap()
:-
#Query("SELECT * FROM workspace_widget_feed_mapping_table JOIN workspace_table ON workSpaceId = workSpaceId_map JOIN widget_table ON widgetId = widgetId_map JOIN feed_table ON feedId = feedId_map")
fun getWorkSpaceWidgetAndFeedFromIntersectionMap(): List<IntersectionWithWorkSpaceWidgetFeed>
A new (or replace existing 6.) section in MainActivity's onCreate method
:-
val iwwfList= dao.getWorkSpaceWidgetAndFeedFromIntersectionMap()
for(iwwf: IntersectionWithWorkSpaceWidgetFeed in iwwfList) {
Log.d("WWFINFO","WorkSpaceID = " + iwwf.workSpace.workSpaceId + " WorkSpaceName = " + iwwf.workSpace.workPlaceName +
" WidgetID = " + iwwf.widget.widgetId + " WidgetName = " + iwwf.widget.widgetName +
" FeedID = " + iwwf.feed.feedId + " FeedName = " + iwwf.feed.feedName
)
}
The Result from the above changes :-
2021-04-04 09:20:34.371 D/WWFINFO: WorkSpaceID = 1 WorkSpaceName = WorkSpace 1 WidgetID = 1 WidgetName = Widget 1 FeedID = 2 FeedName = Feed 2
2021-04-04 09:20:34.371 D/WWFINFO: WorkSpaceID = 1 WorkSpaceName = WorkSpace 1 WidgetID = 1 WidgetName = Widget 1 FeedID = 1 FeedName = Feed 1
2021-04-04 09:20:34.371 D/WWFINFO: WorkSpaceID = 2 WorkSpaceName = WorkSpace 2 WidgetID = 1 WidgetName = Widget 1 FeedID = 2 FeedName = Feed 2
2021-04-04 09:20:34.371 D/WWFINFO: WorkSpaceID = 2 WorkSpaceName = WorkSpace 2 WidgetID = 2 WidgetName = Widget 2 FeedID = 1 FeedName = Feed 1
I'm currently working on an android application using room, with a chinese dictionary where there's a one to many relation between a character and its english definitions.
#Entity(tableName="characters")
data class Character(var mandarin : String) {
#PrimaryKey
var charcterId : Int = 0
}
#Entity(tableName = "definitions",
foreignKeys = [
ForeignKey(
entity = Character::class,
parentColumns = ["character_id"],
childColumns = ["parent_character_id"]
)
]
)
class Definition(
var english : String,
) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "definition_id")
var definitionId : Int = 0
#ColumnInfo(name = "parent_character_id")
var parentCharacterId : Int = 0
}
I have a class that I use to store a character and its list of definitions:
data class Word (
#Embedded val character : Character,
#Relation(
parentColumn = "character_id",
entityColumn = "parent_character_id"
//,associateBy = Junction(JunctionWordXDefinition::class)
)
val definitions : MutableList<Definition>
)
My goal is to be able to allow the user to be able to create lists of these words for an activity, and so this will consist of a Many-To-Many relationship between a topic and the Words.
My question is: As android requires classes used in a junction to be either an entity, or a database view, is it possible to use my One-To-Many Word class in the juction table?
Or do I have to modify it to use a junction table?
These are the classes I've created to try and implement this, but I get an error stating Word needs to be an entity or a database view.
#Entity(tableName = "topics")
data class Topic(
val title: String = ""
) {
#ColumnInfo(name = "topic_id")
var id : Int= 0
}
#Entity(primaryKeys = ["topic_id", "character_id"])
data class JunctionTopicXWord (
val topicId : Int,
val characterId : Int
)
data class Lesson(
#Embedded var topic: Topic,
#Relation(
parentColumn = "topic_id",
entityColumn = "character_id",
associateBy = Junction(JunctionTopicXWord::class)
)
val words : List<Word>
)
There are Nested Relations in Room you can try to use without modifying your entities.
I guess it should be something like that (entity Word don't need changes):
data class Lesson(
#Embedded var topic: Topic,
#Relation(
entity = Character::class,
parentColumn = "character_id",
entityColumn = "character_id",
associateBy = Junction(JunctionTopicXWord::class)
)
val words : List<Word>
)
Let's say I want to do an INNER JOIN between two entities Foo and Bar:
#Query("SELECT * FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
List<FooAndBar> findAllFooAndBar();
Is it possible to force a return type like this?
public class FooAndBar {
Foo foo;
Bar bar;
}
When I try to do that, I get this error:
error: Cannot figure out how to read this field from a cursor.
I've also tried aliasing the table names to match the field names, but that didn't work either.
If this isn't possible, how should I cleanly construct a compatible return type that includes all fields for both entities?
Dao
#Query("SELECT * FROM Foo")
List<FooAndBar> findAllFooAndBar();
Class FooAndBar
public class FooAndBar {
#Embedded
Foo foo;
#Relation(parentColumn = "Foo.bar_id", entityColumn = "Bar.id")
List<Bar> bar;
// If we are sure it returns only one entry
// Bar bar;
//Getter and setter...
}
This solution seems to work, but I'm not very proud of it.
What do you think about it?
Edit: Another solution
Dao, I prefer to explicitly select but "*" will do the job :)
Keep in mind that this solution only works when the fields of both entities are unique. See the comments for more information.
#Query("SELECT Foo.*, Bar.* FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
List<FooAndBar> findAllFooAndBar();
Class FooAndBar
public class FooAndBar {
#Embedded
Foo foo;
#Embedded
Bar bar;
//Getter and setter...
}
edit: since Version 2.2.0-alpha01, room #Relation annotation can manage One-To-One relation
Another option is to just write a new POJO representing the resulting structure of your JOIN query (which even supports column renaming to avoid clashes):
#Dao
public interface FooBarDao {
#Query("SELECT foo.field1 AS unique1, bar.field1 AS unique2 "
+ "FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
public List<FooBar> getFooBars();
static class FooBar {
public String unique1;
public String unique2;
}
}
See: room/accessing-data.html#query-multiple-tables
Try this way.
For example, I have M2M (many-to-many) relations between Product and Attribute. Many Products have many Attributes and I need to get all Attributes by Product.id with sorted records by PRODUCTS_ATTRIBUTES.DISPLAY_ORDERING.
|--------------| |--------------| |-----------------------|
| PRODUCT | | ATTRIBUTE | | PRODUCTS_ATTRIBUTES |
|--------------| |--------------| |-----------------------|
| _ID: Long | | _ID: Long | | _ID: Long |
| NAME: Text | | NAME: Text | | _PRODUCT_ID: Long |
|______________| |______________| | _ATTRIBUTE_ID: Long |
| DISPLAY_ORDERING: Int |
|_______________________|
So, models will be like below:
#Entity(
tableName = "PRODUCTS",
indices = [
Index(value = arrayOf("NAME"), unique = true)
]
)
class Product {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "_ID")
var _id: Long = 0
#ColumnInfo(name = "NAME")
#SerializedName(value = "NAME")
var name: String = String()
}
#Entity(
tableName = "ATTRIBUTES",
indices = [
Index(value = arrayOf("NAME"), unique = true)
]
)
class Attribute {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "_ID")
var _id: Long = 0
#ColumnInfo(name = "NAME")
#SerializedName(value = "NAME")
var name: String = String()
}
And the "join" table will be:
#Entity(
tableName = "PRODUCTS_ATTRIBUTES",
indices = [
Index(value = ["_PRODUCT_ID", "_ATTRIBUTE_ID"])
],
foreignKeys = [
ForeignKey(entity = Product::class, parentColumns = ["_ID"], childColumns = ["_PRODUCT_ID"]),
ForeignKey(entity = Attribute::class, parentColumns = ["_ID"], childColumns = ["_ATTRIBUTE_ID"])
]
)
class ProductAttribute {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "_ID")
var _id: Long = 0
#ColumnInfo(name = "_PRODUCT_ID")
var _productId: Long = 0
#ColumnInfo(name = "_ATTRIBUTE_ID")
var _attributeId: Long = 0
#ColumnInfo(name = "DISPLAY_ORDERING")
var displayOrdering: Int = 0
}
In, AttributeDAO, to get all attributes based on Product._ID, you can do something like below:
#Dao
interface AttributeDAO {
#Query("SELECT ATTRIBUTES.* FROM ATTRIBUTES INNER JOIN PRODUCTS_ATTRIBUTES ON PRODUCTS_ATTRIBUTES._ATTRIBUTE_ID = ATTRIBUTES._ID INNER JOIN PRODUCTS ON PRODUCTS._ID = PRODUCTS_ATTRIBUTES._PRODUCT_ID WHERE PRODUCTS._ID = :productId ORDER BY PRODUCTS_ATTRIBUTES.DISPLAY_ORDERING ASC")
fun getAttributesByProductId(productId: Long): LiveData<List<Attribute>>
}
If you have any questions, please tell me.
Is it possible to force a return type like this?
You can try #Embedded annotations on foo and bar. That will tell Room to try to take the columns from your query and pour them into foo and bar instances. I have only tried this with entities, but the docs indicate that it should work with POJOs as well.
However, this may not work well if your two tables have columns with the same name.
This is my Food Table
#Entity(tableName = "_food_table")
data class Food(#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "_food_id")
var id: Int = 0,
#ColumnInfo(name = "_name")
var name: String? = "")
This is my Cart table and model class (Food Cart)
#Entity(tableName = "_client_cart_table")
data class CartItem(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "_food_id")
var foodId: Int? = 0,
#Embedded(prefix = "_food")
var food: Food? = null,
#ColumnInfo(name = "_branch_id")
var branchId: Int = 0)
Note: Here we see _food_id column in two table. It will throw compile time error. From #Embedded doc, you have to use prefix to differentiate between them.
Inside dao
#Query("select * from _client_cart_table inner join _food_table on _client_cart_table._food_id = _food_table._food_id where _client_cart_table._branch_id = :branchId")
fun getCarts(branchId: Int) : LiveData<List<CartItem>>
This query will return data like this
CartItem(foodId=5, food=Food(id=5, name=Black Coffee), branchId=1)
I have done this in my project. So Give it a try. Happy coding