【问题标题】:How to filter a nested relation in Room?如何过滤 Room 中的嵌套关​​系?
【发布时间】:2018-02-18 02:20:36
【问题描述】:

让我们举个例子:我有一个表格,它有几个部分,每个部分都有问题。顺便说一句,我有映射到问题的答案,并且它们还有另一列我想在查询时过滤:

所以我有以下实体:

@Entity(tableName = "sections")
public class Section {
    @PrimaryKey
    public long id;
    public String title;
}
@Entity(tableName = "questions")
public class Question {
    @PrimaryKey
    public long id;
    public String title;
    public long sectionId;
}
@Entity(tableName = "answers")
public class Answer {
    @PrimaryKey
    public long id;
    public long questionId;
    public int otherColumn;
}

在 DAO 部分,我想检索所有这些。

这是我希望通过此查询填充的 POJO:

class SectionWithQuestions {
    @Embedded
    public Section section;

    @Relation(parentColumn = "id", entityColumn = "sectionId", entity = Question.class)
    public List<QuestionWithAnswer> questions;

    public static class QuestionWithAnswer {
        @Embedded
        public Question question;

        @Relation(parentColumn = "id", entityColumn = "questionId", entity = Answer.class)
        List<Answer> answers;
    }
}

在另一个应用程序中,查询将是:

SELECT s.*, q.*, a.*
FROM sections s
LEFT JOIN questions q ON q.sectionId = s.id
LEFT JOIN answers a ON a.questionId = q.id
WHERE s.id = :sectionId and a.otherColumn = :otherColumn

但是在 Room 中,我发现如果您想要一个对象及其关系(例如示例中的用户及其宠物),您只需选择该对象,然后在第二个查询中查询这些关系。那将是:

@Query("SELECT * FROM sections WHERE id = :sectionId")

那么在生成的代码中就会有(伪代码):

sql = "SELECT * FROM sections WHERE id = :sectionId" // what's inside @Query
cursor = query(sql)
int indexColumn1 = cursor.getColumnIndex(col1)
int indexColumn2
... etc
while (cursor.moveToNext) {
    masterObject = new object()
    masterObject.property1 = cursor.get(indexColumn1)
    ... etc

    __fetchRelationshipXXXAsYYY(masterObject.relations) // fetch the child objects
}

而这个__fetch XXX as YYY方法如下:

sql = "SELECT field1, field2, ... FROM a WHERE foreignId IN (...)"
similar algo as previously: fetch column indices, and loop through the cursor

所以基本上它创建了 2 个查询:一个用于主对象,一个用于关系。第二个查询是自动创建的,我们无法控制它。

要回到我想要关系但还要过滤子列的问题,我被卡住了:

  • 在第一个查询中,我无法引用 otherColumn 列,因为它不存在
  • @Relation 中我也不能,因为此注释的唯一属性是连接列和实体定义

这在 Room 中是否可行,还是我必须自己进行子查询?

额外问题:为什么他们不在一个查询中连接表,而是创建 2 个查询?这是出于性能原因吗?


编辑以澄清我的预期:

这就是我希望写的:

@Query("SELECT s.*, q.*, a.* " +
       "FROM sections s " +
       "LEFT JOIN questions q ON q.sectionId = s.id " +
       "LEFT JOIN answers a ON a.questionId = q.id " +
       "WHERE s.id = :sectionId and a.otherColumn = :additionalIntegerFilter")
SectionWithQuestionsAndAnswers fetchFullSectionData(long sectionId);

static class SectionWithQuestionsAndAnswers {
    @Embedded Section section;
    @Relation(parentColumn = "id", entityColumn = "sectionId", entity = Question.class)
    List<QuestionWithAnswers> questions;
}
static class QuestionWithAnswers {
    @Embedded Question question;
    @Relation(parentColumn = "id", entityColumn = "questionId", entity = Answer.class)
    Answer answer; // I already know that @Relation expects List<> or Set<> which is
                   // not useful if I know I have zero or one relation (ensured
                   // through unique keys)
}

这是我想象的由 Room 实现为生成代码的伪代码:

function fetchFullSectionData(long sectionId, long additionalIntegerFilter) {
    query = prepare(sql); // from @Query
    query.bindLong("sectionId", sectionId);
    query.bindLong("additionalIntegerFilter", additionalIntegerFilter);
    cursor = query.execute();
    Section section = null;
    long prevQuestionId = 0;
    Question question = null;
    while (cursor.hasNext()) {
        if (section == null) {
            section = new Section();
            section.questions = new ArrayList<>();
            section.field1 = cursor.get(col1); // etc for all fields
        }
        if (prevQuestionId != cursor.get(questionIdColId)) {
            if (question != null) {
                section.questions.add(question);
            }
            question = new Question();
            question.fiedl1 = cursor.get(col1); // etc for all fields
            prevQuestionId = question.id;
        }
        if (cursor.get(answerIdColId) != null) { // has answer
            Answer answer = new Answer();
            answer.field1 = cursor.get(col1); // etc for all fields
            question.answer = answer;
        }
    }
    if (section !=null && question != null) {
        section.questions.add(question);
    }
    return section;
}

这是一个查询,我的所有对象都已获取。

【问题讨论】:

  • “在 DAO 部分,我想检索所有这些” - 不是根据您的其余问题。您只想检索具有关联 Answer 且具有特定 otherColumn 值的子集。 “这可能在房间里吗”——不是在一个单独的请求 AFAIK 中。您需要向适当的 DAO 提出适当的请求并将结果拼接在一起。 “为什么他们不在一个查询中连接表,而是创建 2 个查询?这是出于性能原因吗?” ——我猜这就是“天啊,这只是 1.0.0,给我休息一下”的原因。 :-)
  • 我的意思是在另一个应用程序(纯 Java、PHP 等)中,我会使用带有两个连接的请求,这将返回一个部分、所有问题以及答案(如果有) .所以我会得到一个部分,很多问题,每个问题都可能有一个答案。这是一个查询,以及来自该单个查询的许多对象。似乎 Room 无法处理对我所有对象的查询。看来我必须使用一个查询来选择一个部分及其所有问题,然后循环问题并查询答案。这是 DAO 中的 2 个步骤,每个问题 2 个查询 + 1 个查询。
  • “看来 Room 无法处理对我所有对象的查询”——我没有查看生成的代码,例如您的三级层次结构,但是您所描述的不会让我感到惊讶。您可以提交a feature request;我的猜测是,这将是 1.0.0 后的倡议。
  • 实际上第二级关系不是我的问题,我的问题设计得很糟糕。相关的是连接表上的过滤器,无法设置,因为连接表实际上不是JOIN,除了关系ID之外,我无法向关系添加过滤器。我想过滤answer.otherColumn。也许那是一个功能请求。我编辑了我的问题,详细说明了我的预期。
  • 回复:功能请求:posted #65509934

标签: android sqlite android-room


【解决方案1】:

我发现房间关系很难处理,不是很灵活,而且大部分工作都是在幕后完成的,很难真正确定如何处理。

在我的项目中,大部分时间我只是创建演示对象 - 专用于某些 UI 演示的对象,可以用自定义选择填充。

这样我就可以更好地控制我想从 DB 中获取的内容(即我真正需要的内容),并将其填充到自定义演示对象中。

【讨论】:

    猜你喜欢
    • 2018-03-05
    • 1970-01-01
    • 2020-11-22
    • 2017-03-02
    • 2021-10-13
    • 2020-10-15
    • 2015-10-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多