簡體   English   中英

Google Firestore - 如何在一次往返中通過多個 id 獲取多個文檔?

[英]Google Firestore - How to get several documents by multiple ids in one round-trip?

我想知道是否可以在到 Firestore 數據庫的一次往返(網絡調用)中通過 id 列表獲取多個文檔。

如果您在 Node 中:

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

/**
* 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] 現在支持 IN 查詢!”

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

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

他們剛剛宣布了這個功能, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

現在您可以使用類似的查詢,但請注意輸入大小不能大於 10。

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

在實踐中,你會像這樣使用 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())))
}

你可以使用這樣的函數:

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

可以使用單個 ID 調用它:

getById('collection', 'some_id')

或一組 ID:

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

不,目前無法使用 Cloud Firestore SDK 批量處理多個讀取請求,因此無法保證您可以一次讀取所有數據。

然而,正如 Frank van Puffelen 在上面的評論中所說的,這並不意味着獲取 3 個文檔的速度是獲取一個文檔的 3 倍。 在得出結論之前,最好先進行自己的測量。

如果您正在使用顫振,您可以執行以下操作:

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

這將返回一個包含List<DocumentSnapshot>的 Future ,您可以根據需要對其進行迭代。

當然,最好的方法是在 Cloud Function 中實現 Firestore 的實際查詢? 然后只有從客戶端到 Firebase 的一次往返調用,這似乎正是您所要求的。

無論如何,您確實希望像此服務器端一樣保留所有數據訪問邏輯。

在內部,對 Firebase 本身的調用數量可能相同,但它們都將通過 Google 的超快速互連,而不是外部網絡,並結合 Frank van Puffelen 解釋的流水線,您應該獲得出色的性能這種方法。

下面介紹如何使用 Android SDK 在 Kotlin 中執行此類操作。
可能不一定是一次往返,但它確實有效地對結果進行了分組並避免了許多嵌套的回調。

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 向您收取查詢結果集的費用。

我希望這對你有幫助,它對我有用。

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);
          });
        });
    });
  }

對於那些想使用 Angular 做這件事的人,這里有一個例子:

首先需要一些庫導入:(必須預先安裝)

import * as firebase from 'firebase/app'
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'

集合的一些配置:

yourCollection: AngularFirestoreCollection;

constructor(
    private _db : AngularFirestore,
) { 
    // this is your firestore collection
    this.yourCollection = this._db.collection('collectionName');
}

這是執行查詢的方法:('products_id' 是一個 id 數組)

getProducts(products_ids) {
    var queryId = firebase.firestore.FieldPath.documentId();
    this.yourCollection.ref.where(queryId, 'in', products_ids).get()
        .then(({ docs }) => {
            console.log(docs.map(doc => doc.data()))
        })
}

目前在 Firestore 中這似乎是不可能的。 我不明白為什么 Alexander 的回答被接受,他提出的解決方案只是返回“用戶”集合中的所有文檔。

根據您需要做什么,您應該考慮復制您需要顯示的相關數據,並且僅在需要時才請求完整文檔。

對的,這是可能的。 .NET SDK for Firestore 中的示例:

/*List of document references, for example:
    FirestoreDb.Collection(ROOT_LEVEL_COLLECTION).Document(DOCUMENT_ID);*/
    List<DocumentReference> docRefList = YOUR_DOCUMENT_REFERENCE_LIST;
    
    // Required fields of documents, not necessary while fetching entire documents
    FieldMask fieldMask = new FieldMask(FIELD-1, FIELD-2, ...);
    
    // With field mask
    List<DocumentSnapshot> documentSnapshotsMasked = await FirestoreDb.GetAllSnapshotsAsync(docRefList, fieldMask);
    
    // Without field mask
    List<DocumentSnapshot>documentSnapshots = await FirestoreDb.GetAllSnapshotsAsync(docRefList);

.NET 中的文檔:

  1. 獲取所有快照

  2. 場掩碼

您可以使用文檔 ID(最多十個)執行 IN 查詢:

import {
  query,
  collection,
  where,
  getDocs,
  documentId,
} from 'firebase/firestore';

export async function fetchAccounts(
  ids: string[]
) {
  // use lodash _.chunk, for example
  const result = await Promise.all(
    chunk(ids, 10).map(async (chunkIds) => {
      const accounts = await getDocs(
        query(
          collection(firestore, 'accounts'), 
          where(documentId(), 'in', chunkIds)
      ));
      return accounts.docs.filter(doc => doc.exists()).map(doc => doc.data());
    })
  );
  return result.flat(1);
}

對於一些陷入同樣問題的人來說,這里是一個示例代碼:

List<String> documentsIds = {your document ids};

FirebaseFirestore.getInstance().collection("collection_name")
.whereIn(FieldPath.documentId(), documentsIds).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                if (task.isSuccessful()) {
                     for (DocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
                        YourClass object = document.toObject(YourClass.class);
                        // add to your custom list
                    }   
                }
                
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                e.printStackTrace();
            }
        });

使用 Firebase 版本 9(2021 年 12 月更新):

您可以使用“documentId()”“in”“where”子句在一次往返中通過多個id獲取多個文檔

import {
  query,
  collection,
  where,
  documentId,
  getDocs
} from "firebase/firestore";

const q = query(
  collection(db, "products"),
  where(documentId(), "in", 
    [
      "7332ec75-c010-4316-af7f-51170769dca5", 
      "a1e820eb-7f8f-4761-ab00-708a7c836b7d", 
      "acd02b1d-2756-4205-b351-fc376b10ca7f"
    ]
  ),
);

const productDocsSnap = await getDocs(q);

productDocsSnap.forEach((doc) => {
  console.log(doc.data()); // "doc1", "doc2" and "doc3"
});

如果您使用的是 python firebase 管理員 sdk 這就是您使用其 uid 查詢多個文檔的方式

from firebase_admin import firestore
import firebase_admin
from google.cloud.firestore_v1.field_path import FieldPath

app = firebase_admin.initialize_app(cred)
client = firestore.client(app)

collection_ref = client.collection('collection_name')
query = collection_ref.where(FieldPath.document_id(), 'in', listOfIds)
docs = query.get()

for doc in docs:
   print(doc.id, doc.to_dict())

除了導入FieldPath ,您還可以簡單地使用字符串__name__ 現在您的查詢將是collection_ref.where('__name__', 'in', listOfIds)

你能做的就是不能用最好的Promise.all為你的客戶則必須等待.all在繼續之前讀取。

迭代讀取並讓它們獨立解析。 在客戶端,這可能歸結為 UI 有幾個進度加載器圖像獨立解析為值。 但是,這比凍結整個客戶端直到.all讀取解決為止要好。

因此,立即將所有同步結果轉儲到視圖中,然后讓異步結果在解析時單獨進入。 這似乎是微不足道的區別,但是如果您的客戶的 Internet 連接不佳(就像我目前在這家咖啡店的情況),將整個客戶體驗凍結幾秒鍾可能會導致“這個應用程序很糟糕”的體驗。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM