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

البدء

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

الحصول على DatabaseReference

لكتابة بيانات إلى قاعدة البيانات، أنت بحاجة إلى نسخة افتراضية من 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() لحفظ البيانات في مرجع محدّد، واستبدال أي بيانات حالية في ذلك المسار. يمكنك استخدام هذه الطريقة لتمرير الأنواع المقبولة في JSON من خلال نوع خيار متوافق مع ما يلي:

  • Null (يؤدي هذا إلى حذف البيانات)
  • الأعداد الصحيحة (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() لحذف عدة عناصر فرعية في طلب بيانات واحد من واجهة برمجة التطبيقات.

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

لمعرفة وقت ربط بياناتك بخادم قاعدة بيانات Firebase في الوقت الفعلي، تحقَّق من نتيجة المستقبل لتحقيق النجاح.

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

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

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

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

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