حفظ البيانات باستخدام قاعدة بيانات 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 من خلال نوع Variant الذي يدعم:

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

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

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

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