การบันทึกข้อมูลด้วยฐานข้อมูลเรียลไทม์ของ Firebase สำหรับ C++

เริ่ม

ดูคู่มือ Get Started ก่อน หากคุณยังไม่ได้ตั้งค่าแอปและการเข้าถึงฐานข้อมูล

รับข้อมูลอ้างอิงฐานข้อมูล

ในการเขียนข้อมูลไปยังฐานข้อมูล คุณต้องมีอินสแตนซ์ของ 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 ผ่านประเภทตัวแปรที่รองรับ:

  • 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() เพื่อลบรายการย่อยหลายรายการในการเรียก API ครั้งเดียว

รู้ว่าข้อมูลของคุณถูกคอมมิตเมื่อใด

หากต้องการทราบเมื่อข้อมูลของคุณถูกส่งไปยังเซิร์ฟเวอร์ฐานข้อมูลเรียลไทม์ของ Firebase ให้ตรวจสอบผลลัพธ์ ในอนาคต เพื่อดูความสำเร็จ

บันทึกข้อมูลเป็นธุรกรรม

เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการแก้ไขพร้อมกัน เช่น ตัวนับส่วนเพิ่ม คุณสามารถใช้ การดำเนินการธุรกรรม ได้ คุณให้การดำเนินการนี้เป็นฟังก์ชัน DoTransaction ฟังก์ชันอัปเดตนี้ใช้สถานะปัจจุบันของข้อมูลเป็นอาร์กิวเมนต์และส่งคืนสถานะใหม่ที่คุณต้องการเขียน ถ้าไคลเอนต์อื่นเขียนไปยังตำแหน่งก่อนที่ค่าใหม่ของคุณจะเขียนสำเร็จ ฟังก์ชันการอัพเดทของคุณจะถูกเรียกอีกครั้งด้วยค่าปัจจุบันใหม่ และการเขียนจะถูกลองใหม่

ตัวอย่างเช่น ในเกม คุณสามารถอนุญาตให้ผู้ใช้อัปเดตลีดเดอร์บอร์ดที่มีคะแนนสูงสุด 5 อันดับ:

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 จะซิงโครไนซ์ข้อมูลนั้นกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกลและกับไคลเอนต์อื่นๆ บนพื้นฐาน "ความพยายามอย่างดีที่สุด"

ด้วยเหตุนี้ การเขียนทั้งหมดไปยังฐานข้อมูลจะทริกเกอร์เหตุการณ์ในเครื่องทันที ก่อนที่ข้อมูลใดๆ จะถูกเขียนไปยังเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปของคุณยังคงตอบสนองโดยไม่คำนึงถึงเวลาแฝงของเครือข่ายหรือการเชื่อมต่อ

เมื่อสร้างการเชื่อมต่อใหม่แล้ว แอปของคุณจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอนต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบัน โดยไม่ต้องเขียนโค้ดที่กำหนดเองใดๆ

ขั้นตอนถัดไป