حفظ البيانات باستخدام قاعدة بيانات Firebase Realtime لـ C++

البدء

راجع دليل Get Started أولاً إذا لم تكن قد قمت بعد بإعداد تطبيقك والوصول إلى قاعدة البيانات.

الحصول على مرجع قاعدة البيانات

لكتابة البيانات إلى قاعدة البيانات، تحتاج إلى مثيل DatabaseReference :

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

حفظ البيانات

هناك أربع طرق لكتابة البيانات إلى قاعدة بيانات Firebase Realtime:

طريقة الاستخدامات الشائعة
SetValue() كتابة البيانات أو استبدالها في مسار محدد، مثل users/<user-id>/<username> .
PushChild() أضف إلى قائمة البيانات. في كل مرة تتصل فيها بـ Push() ، يُنشئ Firebase مفتاحًا فريدًا يمكن استخدامه أيضًا كمعرف فريد، مثل user-scores/<user-id>/<unique-score-id> .
UpdateChildren() قم بتحديث بعض المفاتيح لمسار محدد دون استبدال كافة البيانات.
RunTransaction() قم بتحديث البيانات المعقدة التي قد تتلف بسبب التحديثات المتزامنة.

كتابة البيانات أو تحديثها أو حذفها في مرجع

عمليات الكتابة الأساسية

بالنسبة لعمليات الكتابة الأساسية، يمكنك استخدام SetValue() لحفظ البيانات في مرجع محدد، واستبدال أي بيانات موجودة في هذا المسار. يمكنك استخدام هذه الطريقة لتمرير الأنواع المقبولة بواسطة JSON من خلال نوع متغير يدعم:

  • فارغة (هذا يحذف البيانات)
  • الأعداد الصحيحة (64 بت)
  • أرقام النقطة العائمة مزدوجة الدقة
  • القيم المنطقية
  • سلاسل
  • ناقلات المتغيرات
  • خرائط السلاسل للمتغيرات

يؤدي استخدام SetValue() بهذه الطريقة إلى الكتابة فوق البيانات الموجودة في الموقع المحدد، بما في ذلك أي عقد فرعية. ومع ذلك، لا يزال بإمكانك تحديث كائن فرعي دون إعادة كتابة الكائن بأكمله. إذا كنت تريد السماح للمستخدمين بتحديث ملفاتهم الشخصية، فيمكنك تحديث اسم المستخدم على النحو التالي:

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

إلحاق بقائمة البيانات

استخدم أسلوب PushChild() لإلحاق البيانات بقائمة في التطبيقات متعددة المستخدمين. تنشئ طريقة PushChild() مفتاحًا فريدًا في كل مرة تتم فيها إضافة طفل جديد إلى مرجع Firebase المحدد. باستخدام هذه المفاتيح التي تم إنشاؤها تلقائيًا لكل عنصر جديد في القائمة، يمكن للعديد من العملاء إضافة عناصر فرعية إلى نفس الموقع في نفس الوقت دون حدوث تعارضات في الكتابة. يعتمد المفتاح الفريد الذي تم إنشاؤه بواسطة PushChild() على طابع زمني، لذلك يتم ترتيب عناصر القائمة تلقائيًا بتسلسل زمني.

يمكنك استخدام المرجع إلى البيانات الجديدة التي يتم إرجاعها بواسطة طريقة PushChild() للحصول على قيمة المفتاح الذي تم إنشاؤه تلقائيًا للطفل أو تعيين البيانات للطفل. يؤدي استدعاء GetKey() على مرجع PushChild() إلى إرجاع قيمة المفتاح الذي تم إنشاؤه تلقائيًا.

تحديث حقول محددة

للكتابة بشكل متزامن إلى فروع محددة لعقدة ما دون الكتابة فوق العقد الفرعية الأخرى، استخدم الأسلوب 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 .

باستخدام هذه المسارات، يمكنك إجراء تحديثات متزامنة لمواقع متعددة في شجرة JSON من خلال استدعاء واحد لـ UpdateChildren() ، مثل كيفية إنشاء هذا المثال للإدخال الجديد في كلا الموقعين. التحديثات المتزامنة التي يتم إجراؤها بهذه الطريقة تكون ذرية: إما أن تنجح كافة التحديثات أو تفشل كافة التحديثات.

حذف البيانات

إن أبسط طريقة لحذف البيانات هي استدعاء RemoveValue() على مرجع لموقع تلك البيانات.

يمكنك أيضًا الحذف عن طريق تحديد Variant null كقيمة لعملية كتابة أخرى مثل SetValue() أو UpdateChildren() . يمكنك استخدام هذه التقنية مع UpdateChildren() لحذف عدة توابع في استدعاء API واحد.

اعرف متى يتم الالتزام ببياناتك.

لمعرفة متى يتم إرسال بياناتك إلى خادم Firebase Realtime Database، تحقق من النتيجة المستقبلية لتحقيق النجاح.

حفظ البيانات كمعاملات

عند العمل مع البيانات التي قد تتلف بسبب التعديلات المتزامنة، مثل العدادات التزايدية، يمكنك استخدام عملية المعاملة . يمكنك إعطاء هذه العملية دالة 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 بعد ذلك بمزامنة تلك البيانات مع خوادم قاعدة البيانات البعيدة ومع العملاء الآخرين على أساس "أفضل جهد".

ونتيجة لذلك، تؤدي جميع عمليات الكتابة إلى قاعدة البيانات إلى تشغيل الأحداث المحلية على الفور، قبل كتابة أي بيانات إلى الخادم. وهذا يعني أن تطبيقك يظل مستجيبًا بغض النظر عن زمن وصول الشبكة أو اتصالها.

بمجرد إعادة تأسيس الاتصال، يتلقى تطبيقك المجموعة المناسبة من الأحداث بحيث يتزامن العميل مع حالة الخادم الحالية، دون الحاجة إلى كتابة أي تعليمات برمجية مخصصة.

الخطوات التالية