Google Firestore - จะรับเอกสารโดยใช้หลายรหัสในการเดินทางรอบเดียวได้อย่างไร


105

ฉันสงสัยว่าเป็นไปได้ไหมที่จะได้รับเอกสารหลายรายการตามรายการรหัสในการเดินทางไปกลับหนึ่งครั้ง (การโทรผ่านเครือข่าย) ไปยัง Firestore


4
ดูเหมือนคุณจะคิดว่า Roundtrips ทำให้เกิดปัญหาด้านประสิทธิภาพในแอปของคุณ ฉันจะไม่คิดอย่างนั้น Firebase มีประวัติในการดำเนินการที่ดีในกรณีดังกล่าวเพราะมันท่อร้องขอ แม้ว่าฉันจะยังไม่ได้ตรวจสอบว่า Firestore ทำงานอย่างไรในสถานการณ์นี้ แต่ฉันก็ชอบที่จะเห็นการพิสูจน์ปัญหาด้านประสิทธิภาพก่อนที่จะสันนิษฐานว่ามีอยู่จริง
Frank van Puffelen

1
สมมติว่าผมต้องใช้เอกสารa, b, cที่จะทำบางสิ่งบางอย่าง ฉันขอทั้งสามพร้อมกันในคำขอแยกกัน aใช้เวลา 100ms bใช้เวลา 150ms และcใช้เวลา 3000ms เป็นผลให้ฉันต้องรอ 3000ms เพื่อทำงาน มันจะเป็นmaxของพวกเขา จะมีความเสี่ยงมากขึ้นเมื่อจำนวนเอกสารที่ต้องดึงมีมาก ขึ้นอยู่กับสถานะเครือข่ายฉันคิดว่านี่อาจกลายเป็นปัญหาได้
จุน

1
จะไม่ส่งทั้งหมดเป็นครั้งเดียวSELECT * FROM docs WHERE id IN (a,b,c)หรือไม่? ฉันไม่เห็นความแตกต่างเนื่องจากการเชื่อมต่อถูกสร้างขึ้นเพียงครั้งเดียวและส่วนที่เหลือจะถูกวางไว้เหนือสิ่งนั้น เวลา (หลังจากการสร้างการเชื่อมต่อครั้งแรก) คือเวลาในการโหลดเอกสารทั้งหมด + การเดินทางไปกลับ 1 ครั้งเหมือนกันสำหรับทั้งสองวิธี หากพฤติกรรมของคุณแตกต่างออกไปคุณสามารถแบ่งปันตัวอย่าง (ตามคำถามที่เชื่อมโยงของฉัน) ได้หรือไม่
Frank van Puffelen

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

FYI สิ่งที่ฉันหมายถึงโดยการเดินทางไปกลับคือเครือข่ายหนึ่งโทรไปยังฐานข้อมูลจากไคลเอนต์ ฉันกำลังถามว่าการสืบค้นหลายรายการจะถูกจัดกลุ่มโดยอัตโนมัติเป็นการเดินทางไปกลับหนึ่งครั้งโดย Firestore หรือการสืบค้นหลายรายการดำเนินการเป็นการเดินทางไปกลับหลายครั้งพร้อมกัน
จุน

คำตอบ:


100

หากคุณอยู่ในโหนด:

https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L701

/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
*   console.log(`First document: ${JSON.stringify(docs[0])}`);
*   console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/

โดยเฉพาะสำหรับเซิร์ฟเวอร์ SDK

อัปเดต: "Cloud Firestore [sdk ฝั่งไคลเอ็นต์] รองรับการสืบค้นข้อมูลแล้ว!"

https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])


29
สำหรับใครก็ตามที่ต้องการเรียกเมธอดนี้ด้วยอาร์เรย์การอ้างอิงเอกสารที่สร้างขึ้นแบบไดนามิกคุณสามารถทำได้เช่นนี้: firestore.getAll (... arrayOfReferences) .hen ()
Horea

1
ฉันขอโทษ @KamanaKisinga ... ฉันไม่ได้ทำเรื่อง firebase มาเกือบปีแล้วและไม่สามารถช่วยอะไรได้ในตอนนี้ (เดี๋ยวก่อนฉันโพสต์คำตอบนี้เมื่อหนึ่งปีที่แล้ววันนี้!)
Nick Franceschina

2
ขณะนี้ SDK ฝั่งไคลเอ็นต์ยังมีฟังก์ชันนี้ด้วย ดูคำตอบของ jeodonara สำหรับตัวอย่าง: stackoverflow.com/a/58780369
Frank van Puffelen

6
คำเตือน: ตัวกรองในถูก จำกัด ไว้ที่ 10 รายการในขณะนี้ ดังนั้นคุณอาจพบว่ามันไร้ประโยชน์เมื่อคุณกำลังจะผลิต
Martin Cremer

9
จริงๆแล้วคุณต้องใช้firebase.firestore.FieldPath.documentId()ไม่ใช่'id'
Maddocks

23

พวกเขาได้ประกาศเพียงฟังก์ชันนี้ https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

ตอนนี้คุณสามารถใช้แบบสอบถามเช่น แต่โปรดทราบว่าขนาดอินพุตต้องไม่เกิน 10

userCollection.where('uid', 'in', ["1231","222","2131"])


มีแบบสอบถาม whereIn แทนที่จะเป็น where และฉันไม่รู้วิธีออกแบบแบบสอบถามสำหรับเอกสารหลายชุดจากรายการรหัสของเอกสารที่เป็นของคอลเลกชันเฉพาะ กรุณาช่วย.
ข้อผิดพลาดในการรวบรวมสิ้นสุด

17
@Compileerrorend ลองดูไหม db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara

ขอบคุณโดยเฉพาะอย่างยิ่งสำหรับfirebase.firestore.FieldPath.documentId()
Ivan Chernykh

10

ไม่ตอนนี้ไม่มีวิธีจัดกลุ่มคำขออ่านจำนวนมากโดยใช้ Cloud Firestore SDK ดังนั้นจึงไม่มีวิธีใดที่จะรับประกันได้ว่าคุณจะอ่านข้อมูลทั้งหมดพร้อมกันได้

อย่างไรก็ตามตามที่ Frank van Puffelen ได้กล่าวไว้ในความคิดเห็นด้านบนนี้ไม่ได้หมายความว่าการดึงเอกสาร 3 ฉบับจะช้าเท่ากับการดึงเอกสาร 1 ชุด ที่ดีที่สุดคือทำการวัดของคุณเองก่อนที่จะได้ข้อสรุปที่นี่


1
สิ่งนี้คือฉันต้องการทราบข้อ จำกัด ทางทฤษฎีเกี่ยวกับประสิทธิภาพของ Firestore ก่อนที่จะย้ายไปที่ Firestore ฉันไม่ต้องการย้ายข้อมูลและตระหนักว่ามันไม่ดีพอสำหรับกรณีการใช้งานของฉัน
จุน

2
สวัสดีมีการรวมกันของ cose ที่นี่ด้วย สมมติว่าฉันได้จัดเก็บรายชื่อ ID ของเพื่อนทั้งหมดของฉันแล้วและจำนวนคือ 500 ฉันสามารถรับรายชื่อได้ในราคาการอ่าน 1 ครั้ง แต่เพื่อแสดงชื่อและ photoURL ของพวกเขาจะต้องเสียค่าใช้จ่าย 500 ครั้ง
Tapas Mukherjee

1
หากคุณกำลังพยายามอ่านเอกสาร 500 ชุดจะใช้เวลาอ่าน 500 ครั้ง หากคุณรวมข้อมูลที่คุณต้องการจากเอกสารทั้ง 500 ชุดไว้ในเอกสารพิเศษชุดเดียวคุณจะอ่านได้เพียงครั้งเดียว ซึ่งเรียกว่าการทำสำเนาข้อมูลเป็นเรื่องปกติในฐานข้อมูล NoSQL ส่วนใหญ่รวมถึง Cloud Firestore
Frank van Puffelen

1
@FrankvanPuffelen ยกตัวอย่างเช่นใน MongoDB คุณสามารถใช้มี objectid เช่นนี้stackoverflow.com/a/32264630/648851
Sitian Liu

2
เช่นเดียวกับที่ @FrankvanPuffelen กล่าวว่าการทำสำเนาข้อมูลเป็นเรื่องปกติในฐานข้อมูล NoSQL ที่นี่คุณต้องถามตัวเองว่าต้องอ่านข้อมูลเหล่านี้บ่อยเพียงใดและต้องเป็นข้อมูลล่าสุดเพียงใด หากคุณเก็บข้อมูลผู้ใช้ไว้ 500 คนสมมติว่าชื่อ + รูปถ่าย + รหัสคุณสามารถอ่านได้ในครั้งเดียว แต่ถ้าคุณต้องการข้อมูลล่าสุดคุณอาจต้องใช้ฟังก์ชันคลาวด์เพื่ออัปเดตข้อมูลอ้างอิงเหล่านี้ทุกครั้งที่ผู้ใช้อัปเดตชื่อ / รูปภาพดังนั้นจึงเรียกใช้ฟังก์ชันคลาวด์ + ดำเนินการเขียนบางอย่าง ไม่มีการใช้งานที่ "ถูกต้อง" / "ดีกว่า" ขึ้นอยู่กับกรณีการใช้งานของคุณ
schankam

10

ในทางปฏิบัติคุณจะใช้ firestore.getAll เช่นนี้

async getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    const users = await this.firestore.getAll(...refs)
    console.log(users.map(doc => doc.data()))
}

หรือด้วยไวยากรณ์สัญญา

getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}

4
นี่ควรเป็นคำตอบที่เลือกไว้จริงๆเพราะให้คุณใช้มากกว่า 10 รหัส
sshah98

10

คุณสามารถใช้ฟังก์ชันเช่นนี้:

function getById (path, ids) {
  return firestore.getAll(
    [].concat(ids).map(id => firestore.doc(`${path}/${id}`))
  )
}

สามารถเรียกได้ด้วย ID เดียว:

getById('collection', 'some_id')

หรืออาร์เรย์ของ IDs:

getById('collection', ['some_id', 'some_other_id'])

5

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

คุณต้องการรักษาตรรกะการเข้าถึงข้อมูลทั้งหมดของคุณเช่นเดียวกับฝั่งเซิร์ฟเวอร์นี้

ภายในจะมีการโทรไปยัง Firebase ในจำนวนเท่ากัน แต่ทั้งหมดจะอยู่ในการเชื่อมต่อระหว่างกันที่รวดเร็วเป็นพิเศษของ Google มากกว่าเครือข่ายภายนอกและเมื่อรวมกับการวางท่อที่ Frank van Puffelen ได้อธิบายไว้คุณควรได้รับประสิทธิภาพที่ยอดเยี่ยมจาก แนวทางนี้


3
การจัดเก็บการนำไปใช้งานใน Cloud Function เป็นการตัดสินใจที่ถูกต้องในบางกรณีที่คุณมีตรรกะที่ซับซ้อน แต่อาจไม่ใช่ในกรณีที่คุณต้องการรวมรายการที่มีหลาย ID สิ่งที่คุณเสียไปคือการแคชฝั่งไคลเอ็นต์และการจัดรูปแบบผลตอบแทนที่เป็นมาตรฐานจากการโทรปกติ สิ่งนี้ทำให้เกิดปัญหาด้านประสิทธิภาพมากกว่าที่จะแก้ไขได้ในบางกรณีในแอปของฉันเมื่อฉันใช้วิธีนี้
เยเรมีย์

5

หากคุณกำลังใช้ Flutter คุณสามารถทำสิ่งต่อไปนี้:

Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();

สิ่งนี้จะส่งคืนอนาคตที่มีList<DocumentSnapshot>ซึ่งคุณสามารถทำซ้ำได้ตามที่คุณต้องการ


2

นี่คือวิธีที่คุณจะทำสิ่งนี้ใน Kotlin ด้วย Android SDK
อาจไม่จำเป็นต้องอยู่ในการเดินทางรอบเดียว แต่จะจัดกลุ่มผลลัพธ์ได้อย่างมีประสิทธิภาพและหลีกเลี่ยงการเรียกกลับที่ซ้อนกันจำนวนมาก

val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }

Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
    //Do what you need to with the document list
}

โปรดทราบว่าการดึงเอกสารเฉพาะจะดีกว่าการดึงเอกสารทั้งหมดและกรองผลลัพธ์ เนื่องจาก Firestore เรียกเก็บเงินจากคุณสำหรับชุดผลลัพธ์แบบสอบถาม


1
ทำงานได้ดีตรงกับสิ่งที่ฉันกำลังมองหา!
Georgi

1

ฉันหวังว่านี่จะช่วยคุณได้มันเหมาะกับฉัน

getCartGoodsData(id) {

    const goodsIDs: string[] = [];

    return new Promise((resolve) => {
      this.fs.firestore.collection(`users/${id}/cart`).get()
        .then(querySnapshot => {
          querySnapshot.forEach(doc => {
            goodsIDs.push(doc.id);
          });

          const getDocs = goodsIDs.map((id: string) => {
            return this.fs.firestore.collection('goods').doc(id).get()
              .then((docData) => {
                return docData.data();
              });
          });

          Promise.all(getDocs).then((goods: Goods[]) => {
            resolve(goods);
          });
        });
    });
  }

0

สิ่งนี้ดูเหมือนจะไม่สามารถทำได้ใน Firestore ในขณะนี้ ฉันไม่เข้าใจว่าทำไมคำตอบของ Alexander จึงเป็นที่ยอมรับวิธีแก้ปัญหาที่เขาเสนอก็เพียงส่งคืนเอกสารทั้งหมดในคอลเล็กชัน "ผู้ใช้"

ขึ้นอยู่กับสิ่งที่คุณต้องทำคุณควรตรวจสอบการทำซ้ำข้อมูลที่เกี่ยวข้องที่คุณต้องการแสดงและขอเอกสารฉบับเต็มเมื่อจำเป็นเท่านั้น


-1

สิ่งที่ดีที่สุดที่คุณทำได้คือไม่ใช้Promise.allเป็นไคลเอนต์ของคุณต้องรอให้.allอ่านก่อนดำเนินการ

อ่านซ้ำและปล่อยให้แก้ไขโดยอิสระ ในฝั่งไคลเอ็นต์สิ่งนี้อาจเกิดขึ้นกับ UI ที่มีอิมเมจตัวโหลดความคืบหน้าหลายภาพที่แก้ไขค่าเป็นอิสระ อย่างไรก็ตามวิธีนี้ดีกว่าการตรึงไคลเอ็นต์ทั้งหมดจนกว่า.allการอ่านจะแก้ไข

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


3
เป็นแบบอะซิงโครนัสมีกรณีการใช้งานมากมายสำหรับการใช้งานPromise.all... ไม่จำเป็นต้อง "หยุด" อะไรเลย - คุณอาจต้องรอข้อมูลทั้งหมดก่อนจึงจะสามารถทำสิ่งที่มีความหมายได้
Ryan Taylor

มีหลายกรณีการใช้งานเมื่อคุณต้องการโหลดข้อมูลทั้งหมดของคุณดังนั้นการรอ (เช่นสปินเนอร์ที่มีข้อความที่เหมาะสมไม่จำเป็นต้อง "หยุด" UI ใด ๆ อย่างที่คุณพูด) Promise.all . มันขึ้นอยู่กับชนิดของผลิตภัณฑ์ที่คุณสร้างที่นี่. ความคิดเห็นประเภทนี้เป็นความคิดเห็นของฉันเองที่ไม่เกี่ยวข้องและไม่ควรมีคำว่า "ดีที่สุด" อยู่ในนั้น มันขึ้นอยู่กับทุกกรณีการใช้งานที่แตกต่างกันและสิ่งที่แอปของคุณกำลังทำเพื่อผู้ใช้
schankam
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.