Структурирование таблиц БД и @Relation для создания вопросника из сущностей БД? (котлин, Комната)

#kotlin #android-room

Вопрос:

Я ищу небольшое руководство, прежде чем погрузиться в новый проект.

Моя цель состоит в том, чтобы создать форму/опросник в приложении, которое извлекает данные о вопросах на основе группы вопросов и их категории. Где каждая Категория содержит много групп, и каждая группа содержит много вопросов.

Для поддержки пользовательского интерфейса и вложенных представлений переработчика целью было предоставить viewmodel один объект, содержащий вложенные списки. т. е. объект со списком категорий, содержащий список групп, содержащий список вопросов.

С точки зрения создания комнат, сущностей и даосов, а также их взаимоотношений, я понимаю, что лучший способ достичь этого-это:

  • Создайте объект вопросов (объединяющий текст,параметры и т. Д.)
  • Создайте справочную таблицу для взаимосвязи между вопросами и группами («многие ко многим»).
  • Создайте родительскую/дочернюю таблицу для взаимосвязи между группами и категориями (один ко многим).

После этого для объединения каждой пары следует использовать набор классов данных, основанных на отношениях.

  • Класс данных GroupWithQuestions (с использованием @Relation для перечисления вопросов в каждой группе с использованием справочной таблицы)
  • Класс данных CategoryWithGroupsWithQuestions (с использованием @Отношение к группам списков в каждой категории с использованием родительской/дочерней таблицы)
  • Класс данных QuestionaireWithCategoriesWith…вопросов (содержащий список категорий с группами вопросов)

Это сложно, связи необходимо отслеживать в нескольких таблицах, и, следовательно, их будет сложно обновить и потребуется много времени для устранения ошибок. Я чувствую, что я переосмыслил подход (или что-то упустил).

Есть ли более простой/разумный способ?

(Является ли подход с одним объектом частью проблемы?)

Заранее спасибо за ваши предложения и комментарии.

Ответ №1:

Создайте объект вопросов (объединяющий текст,параметры и т. Д.) Создайте справочную таблицу для взаимосвязи между вопросами и группами (многие ко многим) Создайте родительскую/дочернюю таблицу для взаимосвязи между группами и категориями (один ко многим)

Одному-многим просто нужна колонка в дочернем для родителя.

Это сложно, связи необходимо отслеживать в нескольких таблицах, и, следовательно, их будет сложно обновить и потребуется много времени для устранения ошибок. Я чувствую, что я переосмыслил подход (или что-то упустил).

На самом деле это не так уж сложно: я считаю, что следующее довольно близко к тому, о чем вы, похоже, просите:-

Сущности

Категория :-

 @Entity(
    indices = [
        Index(value = ["categoryName"],unique = true) /* Assume that a category name should be unique */
    ]
)
data class Category(
    @PrimaryKey
    val categoryId: Long? = null,
    @ColumnInfo
    val categoryName: String
)
 

Группа :-

 @Entity(
    foreignKeys = [
        ForeignKey(
            entity = Category::class,
            parentColumns = ["categoryId"],
            childColumns = ["categoryIdMap"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)

data class Group(
    @PrimaryKey
    val groupId: Long? = null,
    @ColumnInfo(index = true)
    val categoryIdMap: Long,
    val groupName: String
)
 
  • Foriegn Key constraints are not necessary but they help to enforce referential integrity.
    • onDelete and onUpdate aren’t necessary but can be helpful

Question

 @Entity(
)
data class Question(
    @PrimaryKey
    val questionId: Long? = null,
    @ColumnInfo(index = true)
    val questionText: String,
    val questionOption: Int
)
 

Карта групповых вопросов (может быть карта групповых вопросов) :-

 @Entity(
    primaryKeys = ["questionIdMap","groupIdMap"],
    foreignKeys = [
        ForeignKey(
            entity = Question::class,
            parentColumns = ["questionId"],
            childColumns = ["questionIdMap"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE),
        ForeignKey(
            entity = Group::class,
            parentColumns = ["groupId"],
            childColumns = ["groupIdMap"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class QuestionGroupMap(
    val questionIdMap: Long,
    @ColumnInfo(index = true)
    val groupIdMap: Long
)
 

ПОЖО

Групповые вопросы

 data class GroupWithQuestions(
    @Embedded
    val group: Group,
    @Relation(
        entity = Question::class,
        entityColumn = "questionId",
        parentColumn = "groupId",
        associateBy = Junction(
            QuestionGroupMap::class,
            parentColumn = "groupIdMap",
            entityColumn = "questionIdMap"
        )
    )
    val questionList: List<Question>
)
 
  • через карту групп вопросов и, следовательно, ассоциацию и соединение

Категория с группами вопросов

 data class CategoryWithGroupWithQuestions(
    @Embedded
    val category: Category,
    @Relation(entity = Group::class,entityColumn = "categoryIdMap",parentColumn = "categoryId")
    val groupWithQuestionsList: List<GroupWithQuestions>
)
 
  • ПРИМЕЧАНИЕ. Несмотря на то, что вы получаете список запросов Groupwith, указана именно групповая сущность.

Некоторые дополнительные услуги, которые могут быть полезны :-

 data class CategoryWithGroup(
    @Embedded
    val category: Category,
    @Relation(entity = Group::class,entityColumn = "categoryIdMap",parentColumn = "categoryId")
    val group: Group
)

data class GroupWithCategory(
    @Embedded
    val group: Group,
    @Relation(entity = Category::class,entityColumn = "categoryId",parentColumn = "categoryIdMap")
    val category: Category
)
 

Дао

AllDao (т. е. все в одном месте для краткости/удобства) :-

 @Dao
abstract class AllDao {

    @Insert
    abstract fun insert(category: Category): Long
    @Insert
    abstract fun insert(group: Group): Long
    @Insert
    abstract fun insert(question: Question): Long
    @Insert
    abstract fun insert(questionGroupMap: QuestionGroupMap): Long
    @Transaction
    @Query("SELECT * FROM `group`")
    abstract fun getAllGroupsWithCategory(): List<GroupWithCategory>
    @Transaction
    @Query("SELECT * FROM category")
    abstract fun getAllCategoriesWithGroups(): List<CategoryWithGroup>
    @Transaction
    @Query("SELECT * FROM `group`")
    abstract fun getAllGroupsWithQuestions(): List<GroupWithQuestions>

    @Transaction
    @Query("SELECT * FROM category")
    abstract fun getAllCategoriesWithGroupsWithQuestions(): List<CategoryWithGroupWithQuestions>

}
 

класс @Database База данных TheDatabase :-

 @Database(entities = [Category::class,Group::class,Question::class,QuestionGroupMap::class],exportSchema = false,version = 1)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDao(): AllDao

    companion object {
        @Volatile
        private var instance: TheDatabase? = null

        fun getInstance(context: Context): TheDatabase {
            if (instance == null) {
                instance = Room.databaseBuilder(
                    context,
                    TheDatabase::class.java,
                    "thedatabase.db"
                )
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}
 
  • allowMainThreadQueries for brevity/convenience

Наконец, приведение вышеперечисленного в действие в действии приведет к извлечению списка категорий с группами вопросов и выводу его в журнал :-

 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 cat1 = dao.insert(Category(categoryName = "Cat1"))
        val cat2 = dao.insert(Category(categoryName = "Cat2"))
        val cat3 = dao.insert(Category(categoryName = "Cat3"))

        val grp1 = dao.insert(Group(groupName = "Grp1",categoryIdMap = cat1))
        val grp11 = dao.insert(Group(groupName = "Grp11",categoryIdMap = cat1))
        val grp111 = dao.insert(Group(groupName = "Grp111",categoryIdMap = cat1))
        val grp1111 = dao.insert(Group(groupName = "Grp1111",categoryIdMap = cat1))
        val grp2 = dao.insert(Group(groupName = "Grp2",categoryIdMap = cat2))
        val grp22 = dao.insert(Group(groupName = "Grp22",categoryIdMap = cat2))
        val grp3 = dao.insert(Group(groupName = "Grp3",categoryIdMap = cat3))

        val q1 = dao.insert(Question(questionText = "Q1 ....", questionOption = 11110000))
        val q2 = dao.insert(Question(questionText = "Q2....", questionOption = 11010101))
        val q3 = dao.insert(Question(questionText = "Q3....", questionOption = 10000001))
        val q4 = dao.insert(Question(questionText = "Q4....",questionOption = 11000001))
        val q5 = dao.insert(Question(questionText = "Q5....",questionOption = 11100011))

        dao.insert(QuestionGroupMap(q1,grp1))
        dao.insert(QuestionGroupMap(q1,grp2))
        dao.insert(QuestionGroupMap(q1,grp3))
        dao.insert(QuestionGroupMap(q2,grp2))
        dao.insert(QuestionGroupMap(q2,grp22))
        dao.insert(QuestionGroupMap(q3,grp3))
        dao.insert(QuestionGroupMap(q4,grp11))
        dao.insert(QuestionGroupMap(q4,grp111))
        dao.insert(QuestionGroupMap(q4,grp1111))
        dao.insert(QuestionGroupMap(q5,grp22))

        /* extract the data via the geAllCategoriesWithGroupsWithQuestions query*/
        for (cwgwq: CategoryWithGroupWithQuestions in dao.getAllCategoriesWithGroupsWithQuestions()) {
            Log.d(TAG,"Category is ${cwgwq.category.categoryName} ID is ${cwgwq.category.categoryId}, it has ${cwgwq.groupWithQuestionsList.size} groups, which are:-")
            for(gwq: GroupWithQuestions in cwgwq.groupWithQuestionsList) {
                Log.d(TAG,"tGroup is ${gwq.group.groupName} ID is ${gwq.group.groupId}, it has ${gwq.questionList.size} questions, which are:-")
                for(q: Question in gwq.questionList) {
                    Log.d(TAG,"ttQuestion is ${q.questionText} options are ${q.questionOption} ID is ${q.questionId}")
                }
            }
        }

    }
}
 

Результат :-

 D/DBINFO: Category is Cat1 ID is 1, it has 4 groups, which are:-
D/DBINFO:   Group is Grp1 ID is 1, it has 1 questions, which are:-
D/DBINFO:       Question is Q1 .... options are 11110000 ID is 1
D/DBINFO:   Group is Grp11 ID is 2, it has 1 questions, which are:-
D/DBINFO:       Question is Q4.... options are 11000001 ID is 4
D/DBINFO:   Group is Grp111 ID is 3, it has 1 questions, which are:-
D/DBINFO:       Question is Q4.... options are 11000001 ID is 4
D/DBINFO:   Group is Grp1111 ID is 4, it has 1 questions, which are:-
D/DBINFO:       Question is Q4.... options are 11000001 ID is 4
D/DBINFO: Category is Cat2 ID is 2, it has 2 groups, which are:-
D/DBINFO:   Group is Grp2 ID is 5, it has 2 questions, which are:-
D/DBINFO:       Question is Q1 .... options are 11110000 ID is 1
D/DBINFO:       Question is Q2.... options are 11010101 ID is 2
D/DBINFO:   Group is Grp22 ID is 6, it has 2 questions, which are:-
D/DBINFO:       Question is Q2.... options are 11010101 ID is 2
D/DBINFO:       Question is Q5.... options are 11100011 ID is 5
D/DBINFO: Category is Cat3 ID is 3, it has 1 groups, which are:-
D/DBINFO:   Group is Grp3 ID is 7, it has 2 questions, which are:-
D/DBINFO:       Question is Q1 .... options are 11110000 ID is 1
D/DBINFO:       Question is Q3.... options are 10000001 ID is 3
 

Комментарии:

1. Ух ты! @miket это невероятный ответ. Спасибо Вам за время и усилия, потраченные на то, чтобы изложить все эти детали. Да, вы правы, это то, на что я ссылался в отношении настройки сущностей и отношений. Поскольку это более простой подход, в конце концов, я с радостью продолжу и продолжу учиться.