获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

使用适用于 C++ 的 Firebase 实时数据库保存数据

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

开始

如果您尚未设置应用程序和访问数据库,请先参阅Get Started指南。

获取数据库引用

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

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

保存数据

将数据写入 Firebase 实时数据库有四种方法:

方法常见用途
SetValue()将数据写入或替换到定义的路径,例如users/<user-id>/<username>
PushChild()添加到数据列表。每次调用Push()时,Firebase 都会生成一个唯一键,该键也可用作唯一标识符,例如user-scores/<user-id>/<unique-score-id>
UpdateChildren()在不替换所有数据的情况下更新定义路径的一些键。
RunTransaction()更新可能被并发更新损坏的复杂数据。

在引用处写入、更新或删除数据

基本写操作

对于基本的写入操作,您可以使用SetValue()将数据保存到指定的引用,替换该路径中的任何现有数据。您可以使用此方法通过支持以下功能的 Variant 类型传递 JSON 接受的类型:

  • Null(这会删除数据)
  • 整数(64 位)
  • 双精度浮点数
  • 布尔值
  • 字符串
  • 变异向量
  • 字符串到变体的映射

以这种方式使用SetValue()会覆盖指定位置的数据,包括任何子节点。但是,您仍然可以在不重写整个对象的情况下更新子对象。如果你想让用户更新他们的个人资料,你可以按如下方式更新用户名:

dbref.Child("users").Child(userId).Child("username").SetValue(name);

附加到数据列表

使用PushChild()方法将数据附加到多用户应用程序中的列表。每次将新子添加到指定的 Firebase 引用时, PushChild()方法都会生成一个唯一键。通过为列表中的每个新元素使用这些自动生成的键,多个客户端可以同时将子项添加到同一位置而不会发生写冲突。 PushChild()生成的唯一键基于时间戳,因此列表项会自动按时间顺序排列。

您可以使用对PushChild()方法返回的新数据的引用来获取子项的自动生成键的值或为子项设置数据。在PushChild()引用上调用GetKey()会返回自动生成的密钥的值。

更新特定字段

要在不覆盖其他子节点的情况下同时写入节点的特定子节点,请使用UpdateChildren()方法。

调用UpdateChildren()时,您可以通过指定键的路径来更新较低级别的子值。如果数据存储在多个位置以更好地扩展,您可以使用数据扇出更新该数据的所有实例。例如,一个游戏可能有这样一个LeaderboardEntry类:

class LeaderboardEntry {
  std::string uid;
  int score = 0;

 public:
  LeaderboardEntry() {
  }

  LeaderboardEntry(std::string uid, int score) {
    this->uid = uid;
    this->score = score;
  }

  std::map<std::string, Object> ToMap() {
    std::map<string, Variant> result = new std::map<string, Variant>();
    result["uid"] = Variant(uid);
    result["score"] = Variant(score);

    return result;
  }
}

要创建LeaderboardEntry并同时将其更新为最近的分数提要和用户自己的分数列表,游戏使用以下代码:

void WriteNewScore(std::string userId, int score) {
  // Create new entry at /user-scores/$userid/$scoreid and at
  // /leaderboard/$scoreid simultaneously
  std::string key = dbref.Child("scores").PushChild().GetKey();
  LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
  std::map<std::string, Variant> entryValues = entry.ToMap();

  std::map<string, Variant> childUpdates = new std::map<string, Variant>();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

此示例使用PushChild()在节点中创建一个条目,其中包含/scores/$key中所有用户的条目,并同时使用key()检索密钥。然后可以使用该密钥在/user-scores/$userid/$key的用户分数中创建第二个条目。

使用这些路径,您可以通过一次调用UpdateChildren()对 JSON 树中的多个位置执行同步更新,例如本示例如何在两个位置创建新条目。以这种方式进行的同时更新是原子的:要么所有更新都成功,要么所有更新都失败。

删除数据

删除数据的最简单方法是在对该数据位置的引用上调用RemoveValue()

您还可以通过将null Variant指定为另一个写入操作(例如SetValue()UpdateChildren()的值来删除。您可以将此技术与UpdateChildren()结合使用,以在单个 API 调用中删除多个子项。

了解您的数据何时提交。

要知道您的数据何时提交到 Firebase 实时数据库服务器,请检查Future结果是否成功。

将数据保存为事务

当处理可能被并发修改破坏的数据时,例如增量计数器,您可以使用事务操作。你给这个操作一个DoTransaction函数。此更新函数将数据的当前状态作为参数并返回您想要写入的新的所需状态。如果另一个客户端在您的新值成功写入之前写入该位置,则会使用新的当前值再次调用您的更新函数,然后重试写入。

例如,在游戏中,您可以允许用户使用五个最高分更新排行榜:

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() >= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector<MutableData> children = mutableData.children();
      std::vector<MutableData>::iterator it;
      for (it = children.begin(); it != children.end(); ++it) {
        if (!it->value().is_map())
          continue;
        long childScore = (long)it->Child("score").value().int64_value();
        if (childScore < minScore) {
          minScore = childScore;
          minVal = &*it;
        }
      }
      if (minScore > score) {
        // The new score is lower than the existing 5 scores, abort.
        return kTransactionResultAbort;
      }

      // Remove the lowest score.
      children.Remove(minVal);
    }

    // Add the new high score.
    std::map<std::string, Variant> newScoreMap =
      new std::map<std::string, Variant>();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

如果多个用户同时记录分数或客户端有陈旧数据,使用事务可以防止排行榜不正确。如果事务被拒绝,服务器将当前值返回给客户端,客户端使用更新后的值再次运行事务。重复此过程,直到交易被接受或尝试次数过多。

离线写入数据

如果客户端失去网络连接,您的应用程序将继续正常运行。

每个连接到 Firebase 数据库的客户端都维护其自己的任何活动数据的内部版本。写入数据时,先写入这个本地版本。然后,Firebase 客户端会在“尽力”的基础上将该数据与远程数据库服务器和其他客户端同步。

因此,在将任何数据写入服务器之前,所有对数据库的写入都会立即触发本地事件。这意味着无论网络延迟或连接如何,您的应用程序都会保持响应。

重新建立连接后,您的应用程序会收到一组适当的事件,以便客户端与当前服务器状态同步,而无需编写任何自定义代码。

下一步