Zapisywanie danych w Bazie danych czasu rzeczywistego Firebase dla C++

Rozpocznij

Jeśli nie masz jeszcze skonfigurowanej aplikacji i dostępu do bazy danych, najpierw zapoznaj się z przewodnikiem Get Started.

Pobieranie DatabaseReference

Aby zapisywać dane w bazie danych, potrzebujesz instancji DatabaseReference:

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

Zapisywanie danych

Dane można zapisywać w Firebase Realtime Database na 4 sposoby:

Metoda Typowe zastosowania
SetValue() Zapisuj lub zastępuj dane w określonej ścieżce, np.users/<user-id>/<username>.
PushChild() Dodaj do listy danych. Za każdym razem, gdy wywołujesz funkcję Push(), Firebase generuje unikalny klucz, który może być też używany jako unikalny identyfikator, np. user-scores/<user-id>/<unique-score-id>.
UpdateChildren() zaktualizować niektóre klucze dla określonej ścieżki bez zastępowania wszystkich danych;
RunTransaction() Aktualizowanie złożonych danych, które mogą zostać uszkodzone przez równoczesne aktualizacje.

Zapisywanie, aktualizowanie i usuwanie danych w referencji

Podstawowe operacje zapisu

W przypadku podstawowych operacji zapisu możesz użyć funkcji SetValue(), aby zapisać dane w określonym odwołaniu, zastępując wszystkie istniejące dane w tej ścieżce. Za pomocą tej metody możesz przekazywać typy akceptowane przez JSON za pomocą typu Variant, który obsługuje:

  • Null (spowoduje to usunięcie danych)
  • Liczby całkowite (64-bitowe)
  • Liczby zmiennoprzecinkowe podwójnej precyzji
  • Wartości logiczne
  • Struny
  • Wektory wariantów
  • Mapy ciągów znaków do wariantów

Użycie SetValue() w ten sposób spowoduje zastąpienie danych w określonej lokalizacji, w tym wszystkich węzłów podrzędnych. Możesz jednak zaktualizować element podrzędny bez przepisywania całego obiektu. Jeśli chcesz zezwolić użytkownikom na aktualizowanie profili, możesz zaktualizować nazwę użytkownika w ten sposób:

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

Dołączanie do listy danych

Użyj metody PushChild(), aby dołączyć dane do listy w aplikacjach wielodostępnych. Metoda PushChild() generuje unikalny klucz za każdym razem, gdy do określonego odwołania Firebase dodawane jest nowe dziecko. Dzięki używaniu tych automatycznie generowanych kluczy dla każdego nowego elementu na liście kilku klientów może dodawać elementy podrzędne do tej samej lokalizacji w tym samym czasie bez konfliktów zapisu. Klucz unikalny generowany przez PushChild() jest oparty na sygnaturze czasowej, więc elementy listy są automatycznie porządkowane chronologicznie.

Możesz użyć odwołania do nowych danych zwróconych przez metodę PushChild(), aby uzyskać wartość automatycznie wygenerowanego klucza elementu podrzędnego lub ustawić dane elementu podrzędnego. Wywołanie GetKey() na odwołaniu PushChild() zwraca wartość klucza wygenerowanego automatycznie.

Aktualizowanie określonych pól

Aby jednocześnie zapisywać dane w określonych węzłach podrzędnych węzła bez zastępowania innych węzłów podrzędnych, użyj metody UpdateChildren().

Podczas wywoływania funkcji UpdateChildren() możesz aktualizować wartości podrzędne niższego poziomu, podając ścieżkę do klucza. Jeśli dane są przechowywane w wielu lokalizacjach, aby zapewnić lepszą skalowalność, możesz zaktualizować wszystkie ich wystąpienia za pomocą rozsyłania danych. Na przykład gra może mieć klasę LeaderboardEntry w tym stylu:

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;
  }
}

Aby utworzyć LeaderboardEntry i jednocześnie zaktualizować go na liście ostatnich wyników oraz na liście wyników użytkownika, gra używa tego kodu:

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);
}

W tym przykładzie używamy PushChild(), aby utworzyć wpis w węźle zawierającym wpisy wszystkich użytkowników w /scores/$key, i jednocześnie pobrać klucz za pomocą key(). Klucz może być następnie użyty do utworzenia drugiego wpisu w wynikach użytkownika na stronie /user-scores/$userid/$key.

Korzystając z tych ścieżek, możesz jednocześnie aktualizować wiele lokalizacji w drzewie JSON za pomocą jednego wywołania UpdateChildren(). Na przykład w tym przykładzie tworzymy nowy wpis w obu lokalizacjach. Jednoczesne aktualizacje przeprowadzane w ten sposób są niepodzielne: wszystkie aktualizacje się udają lub wszystkie się nie udają.

Usuń dane

Najprostszym sposobem usunięcia danych jest wywołanie funkcji RemoveValue() na odwołaniu do lokalizacji tych danych.

Możesz też usunąć element, podając znak null Variant jako wartość innej operacji zapisu, np. SetValue() lub UpdateChildren(). Możesz użyć tej techniki z UpdateChildren(), aby usunąć wiele elementów podrzędnych w ramach jednego wywołania interfejsu API.

Dowiedz się, kiedy Twoje dane zostaną zatwierdzone.

Aby sprawdzić, kiedy dane zostaną zapisane na serwerze Firebase Realtime Database, sprawdź, czy wynik Future jest pozytywny.

Zapisywanie danych jako transakcji

W przypadku danych, które mogą zostać uszkodzone przez równoczesne modyfikacje, np. liczników przyrostowych, możesz użyć operacji transakcji. Nadajesz tej operacji funkcję DoTransaction. Ta funkcja aktualizacji przyjmuje jako argument bieżący stan danych i zwraca nowy pożądany stan, który chcesz zapisać. Jeśli inny klient zapisze dane w lokalizacji, zanim nowa wartość zostanie zapisana, funkcja aktualizacji zostanie ponownie wywołana z nową bieżącą wartością, a zapis zostanie ponowiony.

Na przykład w grze możesz zezwolić użytkownikom na aktualizowanie tablicy wyników 5 najlepszymi wynikami:

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;
  });
}

Użycie transakcji zapobiega nieprawidłowemu wyświetlaniu tablicy wyników, jeśli wielu użytkowników zarejestruje wyniki w tym samym czasie lub klient miał nieaktualne dane. Jeśli transakcja zostanie odrzucona, serwer zwróci bieżącą wartość do klienta, który ponownie uruchomi transakcję ze zaktualizowaną wartością. Powtarzaj te czynności, aż transakcja zostanie zaakceptowana lub wykonasz zbyt wiele prób.

Zapisywanie danych offline

Jeśli klient utraci połączenie z siecią, aplikacja będzie nadal działać prawidłowo.

Każdy klient połączony z bazą danych Firebase ma własną wewnętrzną wersję aktywnych danych. Gdy dane są zapisywane, najpierw trafiają do tej lokalnej wersji. Klient Firebase synchronizuje te dane z serwerami zdalnej bazy danych i innymi klientami w miarę możliwości.

W rezultacie wszystkie zapisy w bazie danych natychmiast wywołują zdarzenia lokalne, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że aplikacja pozostaje responsywna niezależnie od opóźnienia sieci lub połączenia.

Po ponownym nawiązaniu połączenia aplikacja otrzymuje odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera bez konieczności pisania niestandardowego kodu.

Następne kroki