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

เริ่มต้นใช้งาน

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

รับ DatabaseReference

หากต้องการเขียนข้อมูลลงในฐานข้อมูล คุณต้องมีอินสแตนซ์ของ DatabaseReference ดังนี้

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

กำลังบันทึกข้อมูล

การเขียนข้อมูลลงใน Firebase Realtime Database มี 4 วิธีดังนี้

วิธีการ การใช้งานทั่วไป
SetValue() เขียนหรือแทนที่ข้อมูลในเส้นทางที่กำหนด เช่น users/<user-id>/<username>
PushChild() เพิ่มลงในรายการข้อมูล ทุกครั้งที่คุณเรียกใช้ Push() Firebase จะสร้างคีย์ที่ไม่ซ้ำกันซึ่งสามารถใช้เป็นตัวระบุที่ไม่ซ้ำกันได้ด้วย เช่น user-scores/<user-id>/<unique-score-id>
UpdateChildren() อัปเดตคีย์บางรายการสำหรับเส้นทางที่กำหนดโดยไม่ต้องแทนที่ข้อมูลทั้งหมด
RunTransaction() อัปเดตข้อมูลที่ซับซ้อนซึ่งอาจเสียหายจากการอัปเดตที่เกิดขึ้นพร้อมกัน

เขียน อัปเดต หรือลบข้อมูลที่ข้อมูลอ้างอิง

การดำเนินการเขียนพื้นฐาน

สำหรับการดำเนินการเขียนพื้นฐาน คุณสามารถใช้ SetValue() เพื่อบันทึกข้อมูลไปยังการอ้างอิงที่ระบุ โดยแทนที่ข้อมูลที่มีอยู่ในเส้นทางนั้น คุณสามารถใช้วิธีนี้เพื่อส่งประเภทที่ JSON ยอมรับผ่านประเภท Variant ซึ่งรองรับสิ่งต่อไปนี้

  • Null (การดำเนินการนี้จะลบข้อมูล)
  • จำนวนเต็ม (64 บิต)
  • เลขจุดลอยตัวความแม่นยำสองเท่า
  • บูลีน
  • สตริง
  • เวกเตอร์ของตัวแปร
  • Maps of strings to Variants

การใช้ 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() พร้อมกัน จากนั้นจะใช้คีย์เพื่อสร้างรายการที่ 2 ในคะแนนของผู้ใช้ที่ /user-scores/$userid/$key ได้

เมื่อใช้เส้นทางเหล่านี้ คุณจะอัปเดตหลายตำแหน่งในโครงสร้าง JSON พร้อมกันได้ด้วยการเรียกใช้ UpdateChildren() เพียงครั้งเดียว เช่น วิธีที่ตัวอย่างนี้สร้างรายการใหม่ในทั้ง 2 ตำแหน่ง การอัปเดตพร้อมกันที่ทำด้วยวิธีนี้จะเป็นแบบอะตอม กล่าวคือ การอัปเดตทั้งหมดจะสำเร็จหรือล้มเหลวทั้งหมด

ลบข้อมูล

วิธีที่ง่ายที่สุดในการลบข้อมูลคือการเรียกใช้ RemoveValue() ในการอ้างอิงไปยังตำแหน่งของข้อมูลนั้น

นอกจากนี้ คุณยังลบได้โดยระบุ null Variant เป็นค่าสำหรับการดำเนินการเขียนอื่นๆ เช่น SetValue() หรือ UpdateChildren() คุณสามารถใช้เทคนิคนี้กับ UpdateChildren() เพื่อลบรายการย่อยหลายรายการในการเรียก API ครั้งเดียว

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

หากต้องการทราบว่าระบบส่งข้อมูลไปยังเซิร์ฟเวอร์ Firebase Realtime Database เมื่อใด ให้ตรวจสอบผลลัพธ์ Future เพื่อดูว่าสำเร็จหรือไม่

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

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

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

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

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

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

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