Сохранение данных с помощью базы данных Firebase Realtime для C++

Начать

Если вы еще не настроили свое приложение и не имеете доступа к базе данных, сначала ознакомьтесь с руководством Get Started .

Получить ссылку на базу данных

Для записи данных в базу данных вам понадобится экземпляр DatabaseReference :

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

Сохранение данных

Существует четыре способа записи данных в Firebase Realtime Database :

Метод Распространенное использование
SetValue() Запишите или замените данные по определенному пути, например, users/<user-id>/<username> .
PushChild() Добавить в список данных. Каждый раз, когда вы вызываете Push() , Firebase генерирует уникальный ключ, который также может использоваться как уникальный идентификатор, например, user-scores/<user-id>/<unique-score-id> .
UpdateChildren() Обновите некоторые ключи для определенного пути, не заменяя все данные.
RunTransaction() Обновляйте сложные данные, которые могут быть повреждены одновременными обновлениями.

Запись, обновление или удаление данных по ссылке

Базовые операции записи

Для базовых операций записи можно использовать SetValue() для сохранения данных по указанной ссылке, заменяя любые существующие данные по этому пути. Этот метод можно использовать для передачи типов, принимаемых JSON, через тип Variant, который поддерживает:

  • Null (удаляет данные)
  • Целые числа (64-битные)
  • Числа с плавающей точкой двойной точности
  • Булевы значения
  • Струны
  • Векторы вариантов
  • Карты строк в Варианты

Использование SetValue() таким образом перезаписывает данные в указанном месте, включая любые дочерние узлы. Однако вы все равно можете обновить дочерний элемент, не переписывая весь объект. Если вы хотите разрешить пользователям обновлять свои профили, вы можете обновить имя пользователя следующим образом:

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

Добавить к списку данных

Используйте метод PushChild() для добавления данных в список в многопользовательских приложениях. Метод PushChild() генерирует уникальный ключ каждый раз, когда новый дочерний элемент добавляется в указанную ссылку Firebase. Используя эти автоматически сгенерированные ключи для каждого нового элемента в списке, несколько клиентов могут добавлять дочерние элементы в одно и то же место в одно и то же время без конфликтов записи. Уникальный ключ, сгенерированный PushChild() , основан на временной метке, поэтому элементы списка автоматически упорядочиваются в хронологическом порядке.

Вы можете использовать ссылку на новые данные, возвращаемые методом PushChild() чтобы получить значение автоматически сгенерированного ключа дочернего элемента или установить данные для дочернего элемента. Вызов GetKey() для ссылки PushChild() возвращает значение автоматически сгенерированного ключа.

Обновить определенные поля

Чтобы одновременно записывать данные в определенные дочерние узлы узла, не перезаписывая другие дочерние узлы, используйте метод UpdateChildren() .

При вызове UpdateChildren() вы можете обновить значения дочерних элементов нижнего уровня, указав путь для ключа. Если данные хранятся в нескольких местах для лучшего масштабирования, вы можете обновить все экземпляры этих данных с помощью data fan-out . Например, в игре может быть класс 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&ltstd::string, Object&gt ToMap() {
    std::map&ltstring, Variant&gt result = new std::map&ltstring, Variant&gt();
    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&ltstd::string, Variant&gt entryValues = entry.ToMap();

  std::map&ltstring, Variant&gt childUpdates = new std::map&ltstring, Variant&gt();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

В этом примере PushChild() используется для создания записи в узле, содержащей записи для всех пользователей в /scores/$key и одновременного извлечения ключа с помощью key() . Затем ключ можно использовать для создания второй записи в счетах пользователя в /user-scores/$userid/$key .

Используя эти пути, вы можете выполнять одновременные обновления в нескольких местах в дереве JSON с помощью одного вызова UpdateChildren() , например, как этот пример создает новую запись в обоих местах. Одновременные обновления, выполненные таким образом, являются атомарными: либо все обновления завершаются успешно, либо все обновления завершаются неудачей.

Удалить данные

Самый простой способ удалить данные — вызвать RemoveValue() для ссылки на местоположение этих данных.

Вы также можете удалить, указав null Variant в качестве значения для другой операции записи, такой как SetValue() или UpdateChildren() . Вы можете использовать эту технику с UpdateChildren() для удаления нескольких дочерних элементов за один вызов API.

Знайте, когда ваши данные передаются.

Чтобы узнать, когда ваши данные будут переданы на сервер Firebase Realtime Database , проверьте результат Future на предмет успешности.

Сохраните данные как транзакции

При работе с данными, которые могут быть повреждены параллельными изменениями, такими как инкрементные счетчики, вы можете использовать транзакционную операцию . Вы передаете этой операции функцию DoTransaction . Эта функция обновления принимает текущее состояние данных в качестве аргумента и возвращает новое желаемое состояние, которое вы хотите записать. Если другой клиент записывает данные в местоположение до того, как ваше новое значение будет успешно записано, ваша функция обновления вызывается снова с новым текущим значением, и запись повторяется.

Например, в игре вы можете разрешить пользователям обновлять таблицу лидеров, добавляя в нее пять наивысших результатов:

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() &gt= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector&ltMutableData&gt children = mutableData.children();
      std::vector&ltMutableData&gt::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 &lt minScore) {
          minScore = childScore;
          minVal = &amp*it;
        }
      }
      if (minScore &gt 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&ltstd::string, Variant&gt newScoreMap =
      new std::map&ltstd::string, Variant&gt();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

Использование транзакции предотвращает некорректность таблицы лидеров, если несколько пользователей одновременно записывают результаты или у клиента были устаревшие данные. Если транзакция отклонена, сервер возвращает текущее значение клиенту, который снова запускает транзакцию с обновленным значением. Это повторяется до тех пор, пока транзакция не будет принята или пока не будет сделано слишком много попыток.

Запись данных в автономном режиме

Если клиент потеряет сетевое соединение, ваше приложение продолжит работать корректно.

Каждый клиент, подключенный к базе данных Firebase, поддерживает собственную внутреннюю версию любых активных данных. Когда данные записываются, они сначала записываются в эту локальную версию. Затем клиент Firebase синхронизирует эти данные с удаленными серверами баз данных и с другими клиентами по принципу «наилучших усилий».

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

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

Следующие шаги