使用 Firebase Realtime Database 检索数据 (C++)

本文档将介绍数据检索的基础知识以及如何对 Firebase 数据进行排序和过滤。

准备工作

确保您已设置好自己的应用,并且可以按照 Get Started 指南中介绍的方法访问数据库。

检索数据

您可以通过一次性调用 GetValue() 或将某个 ValueListener 附加到 FirebaseDatabase 引用来检索 Firebase 数据。值监听器在数据进入初始状态时会被调用一次,以后每当数据发生更改时都会被再次调用。

获取 DatabaseReference

如需将数据写入到数据库中,您需要一个 DatabaseReference 实例:

    // Get the root reference location of the database.
    firebase::database::DatabaseReference dbref = database->GetReference();

读取数据一次

您可以使用 GetValue() 方法一次性读取某个给定路径下全部内容的静态快照。任务结果将包括一个快照,其中包含该位置下的所有数据,包括子数据。如果该位置没有任何数据,则返回的快照为 null

  firebase::Future&ltfirebase::database::DataSnapshot&gt result =
    dbRef.GetReference("Leaders").GetValue();

此时请求已经发出,但我们必须等待 Future 变成已完成状态之后才能读取所需的值。由于游戏通常在一个循环中运行,并且比起其他应用较少采用回调驱动方式,因此您通常会采用轮询方式来判断操作是否完成。

  // In the game loop that polls for the result...

  if (result.status() != firebase::kFutureStatusPending) {
    if (result.status() != firebase::kFutureStatusComplete) {
      LogMessage("ERROR: GetValue() returned an invalid result.");
      // Handle the error...
    } else if (result.error() != firebase::database::kErrorNone) {
      LogMessage("ERROR: GetValue() returned error %d: %s", result.error(),
                 result.error_message());
      // Handle the error...
    } else {
      firebase::database::DataSnapshot snapshot = result.result();
      // Do something with the snapshot...
    }
  }

这里演示了一些基本的错误检查,如需详细了解错误检查以及如何确定结果何时准备就绪,请参阅 firebase::Future 参考文档。

监听事件

您可以添加监听器来接收数据更改信息:

ValueListener 基类

回拨 典型用法
OnValueChanged 读取并监听对路径中所有内容的更改。

OnChildListener 基类

OnChildAdded 检索项列表,或监听项列表中是否添加了新项。 建议与 OnChildChangedOnChildRemoved 配合使用以监控列表的更改。
OnChildChanged 监听列表中的项是否发生了更改。与 OnChildAddedOnChildRemoved 配合使用以监控列表的更改。
OnChildRemoved 监听列表中是否有项被移除。与 OnChildAddedOnChildChanged 配合使用以监控列表的更改。
OnChildMoved 监听有序列表的项顺序是否有更改。 每当发生 OnChildChanged 回调并且项目的顺序发生更改(基于您当前的排序依据方法)时,就会触发 OnChildMoved 回调。

ValueListener 类

您可以使用 OnValueChanged 回调函数来接收有关某个给定路径下内容发生更改的信息。此回调函数在附加监听器时触发一次,以后会在每次数据(包括子节点数据)发生更改时再次触发。系统会向此回调函数传递一个包含该位置下所有数据(包括子节点数据)的快照。如果该位置没有任何数据,则返回的快照为 null

以下示例演示了一款游戏如何从数据库中检索排行榜得分情况:

  class LeadersValueListener : public firebase::database::ValueListener {
   public:
    void OnValueChanged(
        const firebase::database::DataSnapshot& snapshot) override {
      // Do something with the data in snapshot...
    }
    void OnCancelled(const firebase::database::Error& error_code,
                     const char* error_message) override {
      LogMessage("ERROR: LeadersValueListener canceled: %d: %s", error_code,
                 error_message);
    }
  };

  // Elsewhere in the code...

  LeadersValueListener* listener = new LeadersValueListener();
  firebase::Future&ltfirebase::database::DataSnapshot&gt result =
    dbRef.GetReference("Leaders").AddValueListener(listener);

Future&ltDataSnapshot&gt 结果中包含事件发生时数据库中指定位置存在的数据。调用快照中的 value() 可返回一个表示数据的 Variant

此示例中还重写了 OnCancelled 方法,以判断读取操作是否已被取消。例如,如果客户端没有权限从 Firebase 数据库位置读取数据,则可取消读取操作。database::Error 中的信息将指明操作失败的原因。

ChildListener 类

当因为某项操作(例如通过 PushChild() 方法添加新的子节点,或通过 UpdateChildren() 方法更新子节点)而使得某个节点的子节点发生更改时,就会触发子节点事件。同时使用所有这些事件有助于监听数据库中某个特定节点是否有更改。例如,一款游戏可以综合使用这些方法来监控游戏会话评论中的活动,如下所示:

  class SessionCommentsChildListener : public firebase::database::ChildListener {
   public:
    void OnChildAdded(const firebase::database::DataSnapshot& snapshot,
                      const char* previous_sibling) override {
      // Do something with the data in snapshot ...
    }
    void OnChildChanged(const firebase::database::DataSnapshot& snapshot,
                        const char* previous_sibling) override {
      // Do something with the data in snapshot ...
    }
    void OnChildRemoved(
        const firebase::database::DataSnapshot& snapshot) override {
      // Do something with the data in snapshot ...
    }
    void OnChildMoved(const firebase::database::DataSnapshot& snapshot,
                      const char* previous_sibling) override {
      // Do something with the data in snapshot ...
    }
    void OnCancelled(const firebase::database::Error& error_code,
                     const char* error_message) override {
      LogMessage("ERROR: SessionCommentsChildListener canceled: %d: %s",
                 error_code, error_message);
    }
  };

  // elsewhere ....

  SessionCommentsChildListener* listener = new SessionCommentsChildListener();
  firebase::Future&ltfirebase::database::DataSnapshot&gt result =
    dbRef.GetReference("GameSessionComments").AddChildListener(listener);

OnChildAdded 回调函数通常用于在 Firebase 数据库中检索项目列表。每个现有的子节点都会让系统调用一次 OnChildAdded 回调函数,并且以后每次向指定的路径添加新的子节点时都会再次调用该回调函数。系统会向监听器传递一个包含新子节点数据的快照。

每当有子节点被修改后,系统都会调用 OnChildChanged 回调函数,这包括对子节点的后代所做的任何修改。它通常与 OnChildAddedOnChildRemoved 调用结合使用,以响应对项目列表的更改。传递给监听器的快照中将包含子节点更新后的数据。

OnChildRemoved 回调函数会在直接子节点被移除时触发。它通常与 OnChildAddedOnChildChanged 回调函数结合使用。传递给回调函数的快照中将包含已移除的子节点的数据。

每当引发 OnChildChanged 调用的子节点数据更新操作还会导致子节点重新排序时,系统就会触发 OnChildMoved 回调函数。该函数用于通过 OrderByChildOrderByValue 排序的数据。

排序和过滤数据

您可以使用 Realtime Database Query 类来检索按键、按值或按子项的值排序的数据。您还可以对排序后的结果进行过滤,从而得到特定数量的结果或一系列键或值。

将数据排序

如需检索经过排序的数据,请先指定一种排序依据方法,确定如何对结果排序:

方法 用法
OrderByChild() 按指定子键的值对结果排序。
OrderByKey() 按子键对结果排序。
OrderByValue() 按子值对结果排序。

您每次只能使用一种排序依据方法。在同一查询中多次调用排序依据方法会引发错误。

以下示例演示了如何订阅按得分排序的得分排行榜信息。

  firebase::database::Query query =
    dbRef.GetReference("Leaders").OrderByChild("score");

  // To get the resulting DataSnapshot either use query.GetValue() and poll the
  // future, or use query.AddValueListener() and register to handle the
  // OnValueChanged callback.

此示例定义了一个 firebase::Query,如果将它与 ValueListener 结合使用,可使客户端数据与数据库中的排行榜同步,并按每个条目的得分进行排序。如需详细了解如何高效地设计您的数据结构,请参阅设计数据库的数据结构

调用 OrderByChild() 方法可指定结果排序所依据的子键。在此示例中,系统将按每个子项中的 "score" 值对结果进行排序。如需详细了解如何对其他数据类型进行排序,请参阅如何对查询数据进行排序

过滤数据

如需过滤数据,您可以在构建查询时将某种限制方法或范围方法与排序依据方法结合使用。

方法 用法
LimitToFirst() 设置要返回的项数上限:从经过排序的结果列表开头算起。
LimitToLast() 设置要返回的项数上限:从经过排序的结果列表结尾算起。
StartAt() 返回大于或等于指定键或值的项,具体取决于所选的排序依据方法。
EndAt() 返回小于或等于指定键或值的项,具体取决于所选的排序依据方法。
EqualTo() 返回等于指定键或值的项,具体取决于所选的排序依据方法。

与排序依据方法不同,您可以结合使用多种限制或范围函数。 例如,您可以结合使用 StartAt()EndAt() 方法来将查询结果限制在指定的取值范围内。

即使查询仅存在一个匹配项时,该快照仍是一个仅包含单个项目的列表。

限制结果数

您可以使用 LimitToFirst()LimitToLast() 方法来设置当运行给定回调函数时要同步的子项数上限。例如,如果使用 LimitToFirst() 将数量上限设为 100,则起初最多只会收到 100 次 OnChildAdded 回调。如果您在 Firebase 数据库中存储的项不到 100 个,则每个项均会触发一次 OnChildAdded 回调函数。

随着项发生更改,对于落入查询范围的项,您将收到 OnChildAdded 回调函数;对于不再属于查询范围的项,您将收到 OnChildRemoved 回调函数,因此总数始终保持为 100。

例如,以下代码将返回排行榜中的最高分:

  firebase::database::Query query =
    dbRef.GetReference("Leaders").OrderByChild("score").LimitToLast(1);

  // To get the resulting DataSnapshot either use query.GetValue() and poll the
  // future, or use query.AddValueListener() and register to handle the
  // OnValueChanged callback.

按键或值过滤

您可以使用 StartAt()EndAt()EqualTo() 为查询选择任意起点、终点和等值点。这对于将数据分页或查找其子项具有特定值的项非常有用。

如何对查询数据进行排序

本部分将介绍如何通过 Query 类中的每种排序依据方法对数据进行排序。

OrderByChild

使用 OrderByChild() 时,包含指定子键的数据将按以下方式排序:

  1. 指定子项键的值为 null 的子项排在最前面。
  2. 接下来是指定子项键的值为 false 的子项。如果多个子项的值均为 false,则按照键以字典顺序对它们进行排序。
  3. 接下来是指定子项键的值为 true 的子项。如果多个子项的值均为 true,则按照键以字典顺序对它们进行排序。
  4. 接下来是值为数字的子项,按升序排序。如果指定子节点的多个子项具有相同的数字值,则按照键对它们进行排序。
  5. 值为字符串的子项排列在值为数字的子项后面,并按字典顺序以升序排列。如果指定子节点的多个子项具有相同的值,则按照键以字典顺序对它们进行排序。
  6. 值为对象的子项放在最后,并按照键以字典顺序排序(升序)。

OrderByKey

使用 OrderByKey() 对数据进行排序时,系统会按照键以升序返回数据。

  1. 键可以解析为 32 位整数的子项排在最前,按升序排列。
  2. 以字符串值作为键的子项紧随其后,按字典顺序以升序排列。

OrderByValue

使用 OrderByValue() 时,系统将按子项本身的值对其进行排序。排序标准与 OrderByChild() 中相同,但这里使用的是节点本身的值而非指定子键的值。

后续步骤