简体   繁体   English

Google Firestore - 如何在一次往返中通过多个 id 获取多个文档?

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

I am wondering if it's possible to get multiple documents by a list of ids in one round trip (network call) to the Firestore database.我想知道是否可以在到 Firestore 数据库的一次往返(网络调用)中通过 id 列表获取多个文档。

if you're within Node:如果您在 Node 中:

https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L978 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])}`);
* });
*/

This is specifically for the server SDK这是专门针对服务器SDK的

UPDATE: "Cloud Firestore [client-side sdk] Now Supports IN Queries!"更新: “Cloud Firestore [客户端 sdk] 现在支持 IN 查询!”

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

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

They have just announced this functionality, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .他们刚刚宣布了这个功能, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

Now you can use queries like, but mind that the input size can't be greater than 10.现在您可以使用类似的查询,但请注意输入大小不能大于 10。

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

In practise you would use firestore.getAll like this在实践中,你会像这样使用 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()))
}

or with promise syntax或使用承诺语法

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

You could use a function like this:你可以使用这样的函数:

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

It can be called with a single ID:可以使用单个 ID 调用它:

getById('collection', 'some_id')

or an array of IDs:或一组 ID:

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

No, right now there is no way to batch multiple read requests using the Cloud Firestore SDK and therefore no way to guarantee that you can read all of the data at once.不,目前无法使用 Cloud Firestore SDK 批量处理多个读取请求,因此无法保证您可以一次读取所有数据。

However as Frank van Puffelen has said in the comments above this does not mean that fetching 3 documents will be 3x as slow as fetching one document.然而,正如 Frank van Puffelen 在上面的评论中所说的,这并不意味着获取 3 个文档的速度是获取一个文档的 3 倍。 It is best to perform your own measurements before reaching a conclusion here.在得出结论之前,最好先进行自己的测量。

If you are using flutter, you can do the following:如果您正在使用颤振,您可以执行以下操作:

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

This will return a Future containing List<DocumentSnapshot> which you can iterate as you feel fit.这将返回一个包含List<DocumentSnapshot>的 Future ,您可以根据需要对其进行迭代。

Surely the best way to do this is by implementing the actual query of Firestore in a Cloud Function?当然,最好的方法是在 Cloud Function 中实现 Firestore 的实际查询? There would then only be a single round trip call from the client to Firebase, which seems to be what you're asking for.然后只有从客户端到 Firebase 的一次往返调用,这似乎正是您所要求的。

You really want to be keeping all of your data access logic like this server side anyway.无论如何,您确实希望像此服务器端一样保留所有数据访问逻辑。

Internally there will likely be the same number of calls to Firebase itself, but they would all be across Google's super-fast interconnects, rather than the external network, and combined with the pipelining which Frank van Puffelen has explained, you should get excellent performance from this approach.在内部,对 Firebase 本身的调用数量可能相同,但它们都将通过 Google 的超快速互连,而不是外部网络,并结合 Frank van Puffelen 解释的流水线,您应该获得出色的性能这种方法。

Here's how you would do something like this in Kotlin with the Android SDK.下面介绍如何使用 Android SDK 在 Kotlin 中执行此类操作。
May not necessarily be in one round trip, but it does effectively group the result and avoid many nested callbacks.可能不一定是一次往返,但它确实有效地对结果进行了分组并避免了许多嵌套的回调。

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
}

Note that fetching specific documents is much better than fetching all documents and filtering the result.请注意,获取特定文档比获取所有文档并过滤结果要好得多。 This is because Firestore charges you for the query result set.这是因为 Firestore 向您收取查询结果集的费用。

I hope this helps you, it works for me.我希望这对你有帮助,它对我有用。

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

For the ones who want to do it using Angular, here is an example:对于那些想使用 Angular 做这件事的人,这里有一个例子:

First some library imports are needed: (must be preinstalled)首先需要一些库导入:(必须预先安装)

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

Some configuration for the collection:集合的一些配置:

yourCollection: AngularFirestoreCollection;

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

Here is the method to do the query: ('products_id' is an Array of ids)这是执行查询的方法:('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()))
        })
}

This doesn't seem to be possible in Firestore at the moment.目前在 Firestore 中这似乎是不可能的。 I don't understand why Alexander's answer is accepted, the solution he proposes just returns all the documents in the "users" collection.我不明白为什么 Alexander 的回答被接受,他提出的解决方案只是返回“用户”集合中的所有文档。

Depending on what you need to do, you should look into duplicating the relevant data you need to display and only request a full document when needed.根据您需要做什么,您应该考虑复制您需要显示的相关数据,并且仅在需要时才请求完整文档。

Yes, it is possible.对的,这是可能的。 Sample in .NET SDK for Firestore: .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);

Documentation in .NET: .NET 中的文档:

  1. Get all snapshots 获取所有快照

  2. Field mask 场掩码

You can perform an IN query with the document IDs (up to ten):您可以使用文档 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);
}

For some who are stucked in same problem here is a sample code:对于一些陷入同样问题的人来说,这里是一个示例代码:

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

With Firebase Version 9 (Dec, 2021 Update):使用 Firebase 版本 9(2021 年 12 月更新):

You can get multiple documents by multiple ids in one round-trip using "documentId()" and "in" with "where" clause:您可以使用“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"
});

if you are using the python firebase admin sdk this is how you query for multiple documents using their uids如果您使用的是 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())

Instead of importing FieldPath you can also simply use the string __name__ .除了导入FieldPath ,您还可以简单地使用字符串__name__ Now your query will be collection_ref.where('__name__', 'in', listOfIds)现在您的查询将是collection_ref.where('__name__', 'in', listOfIds)

The best you can do is not use Promise.all as your client then must wait for .all the reads before proceeding.你能做的就是不能用最好的Promise.all为你的客户则必须等待.all在继续之前读取。

Iterate the reads and let them resolve independently.迭代读取并让它们独立解析。 On the client side, this probably boils down to the UI having several progress loader images resolve to values independently.在客户端,这可能归结为 UI 有几个进度加载器图像独立解析为值。 However, this is better than freezing the whole client until .all the reads resolve.但是,这比冻结整个客户端直到.all读取解决为止要好。

Therefore, dump all the synchronous results to the view immediately, then let the asynchronous results come in as they resolve, individually.因此,立即将所有同步结果转储到视图中,然后让异步结果在解析时单独进入。 This may seem like petty distinction, but if your client has poor Internet connectivity (like I currently have at this coffee shop), freezing the whole client experience for several seconds will likely result in a 'this app sucks' experience.这似乎是微不足道的区别,但是如果您的客户的 Internet 连接不佳(就像我目前在这家咖啡店的情况),将整个客户体验冻结几秒钟可能会导致“这个应用程序很糟糕”的体验。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用 Flutter 在一次往返中从 firestore 获取超过 10 个文档 - get more than 10 documents from firestore in one round trip using Flutter Java,Firestore - 在 android 工作室中使用 arraylist 的文档 ID 从 Firestore 获取多个文档 - Java, Firestore - Get multiple documents from firestore using an arraylist of document ids in android studio 如何在 Firestore 中一次获取一组文档 ID 的所有文档,但只获取这些文档中的部分属性,而不是整个文档? - How to get all documents at once for a set of doc IDs, but only part of the properties in those docs, not the entire docs, in Firestore? 如何在一行 LazyColumn 中显示 Firestore 中的多个文档字段 - How to show a multiple fields of documents in Firestore in one row of LazyColumn Flutter Firebase Cloud Firestore 通过 ID 获取文档列表 stream - Flutter Firebase Cloud Firestore get stream of list of documents by their ids 如何在 Firestore 中加入多个文档? - How to join multiple documents in a Firestore? 如何从 firestore 一次获取多个集合组的文档? - How to get multiple collection group's documents at once from firestore? 如何获取flutter中Firestore文档的个数 - How to get the number of Firestore documents in flutter Firestore:如何获取集合中的随机文档 - Firestore: How to get random documents in a collection 多个文档的 Firestore 实时更新? - Firestore realtime updates on several documents?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM