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

البدء

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

الحصول على DatabaseReference

لكتابة بيانات إلى قاعدة البيانات، أنت بحاجة إلى نسخة افتراضية من 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 من خلال نوع السعر المتغير الذي يتيح ما يلي:

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

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

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

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

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

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

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

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