Мне интересно, можно ли получить несколько документов по списку идентификаторов за один проход (сетевой вызов) в базу данных Firestore.

Joon

Ответы (15)

, если вы находитесь внутри узла:

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>} 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»])

Если вы используете флаттер, вы можете сделать следующее:

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

Это вернет Future, содержащий List , который вы можете повторять по своему усмотрению.

Они только что анонсировали эту функциональность, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html.

Теперь вы можете использовать такие запросы, но учтите, что размер ввода не может быть больше 10.

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

Вы можете выполнить запрос IN с идентификаторами документов (до десяти):

импорт {
    запрос,
    коллекция,
    где,
    getDocs,
    documentId,
} из firebase / firestore;

экспортировать асинхронную функцию fetchAccounts (
    ids: строка []
) {
    // используйте lodash _.chunk, например
    const результат = ожидание Promise.all (
        chunk (ids, 10) .map (async (chunkIds) => {
            const accounts = await getDocs (
                запрос(
                    коллекция (firestore, "счета"),
                    где (documentId (), 'in', chunkIds)
                ));
            вернуть accounts.docs.filter (doc => doc.exists ()). map (doc => doc.data ());
        })
    );
    вернуть result.flat (1);
}

Да, это возможно. Пример в .NET SDK для Firestore:

/*List of document references, for example:
    FirestoreDb.Collection(ROOT_LEVEL_COLLECTION).Document(DOCUMENT_ID);*/
    List 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 documentSnapshotsMasked = await FirestoreDb.GetAllSnapshotsAsync(docRefList, fieldMask);
    
    // Without field mask
    ListdocumentSnapshots = await FirestoreDb.GetAllSnapshotsAsync(docRefList);

Документация в .NET:

  1. Получить все снимки

  2. Маска поля

Лучшее, что вы можете сделать, это не используйте Promise.all в качестве вашего клиента, тогда он должен ждать .all чтения до продолжается.

Итерируйте чтения и позвольте им разрешиться независимо. На стороне клиента это, вероятно, сводится к тому, что в пользовательском интерфейсе несколько изображений загрузчика выполнения разрешаются в значения независимо. Однако это лучше, чем замораживание всего клиента до тех пор, пока .all не разрешится чтение.

Следовательно, выгрузите все синхронные результаты в представление немедленно, а затем позвольте асинхронным результатам поступать по мере их разрешения, индивидуально. Это может показаться мелким различием, но если у вашего клиента плохое подключение к Интернету (как у меня сейчас в этом кафе), замораживание всего клиентского опыта на несколько секунд, скорее всего, приведет к тому, что это приложение - отстой.

Для тех, кто застрял в той же проблеме вот пример кода:

List documentsIds = {your document ids};

FirebaseFirestore.getInstance().collection("collection_name")
.whereIn(FieldPath.documentId(), documentsIds).get().addOnCompleteListener(new OnCompleteListener() {
            @Override
            public void onComplete(@NonNull Task 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();
            }
        });

Надеюсь, это вам поможет, у меня работает.

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

В настоящий момент это невозможно в Firestore. Я не понимаю, почему ответ Александра принят, предлагаемое им решение просто возвращает все документы из коллекции «users».

В зависимости от того, что вам нужно сделать, вам следует подумать о дублировании соответствующих данных, которые вам нужно отобразить, и запрашивать полный документ только при необходимости.

Нет, сейчас нет возможности пакетировать несколько запросов на чтение с помощью Cloud Firestore SDK и, следовательно, нет способа гарантировать, что вы можете прочитать все данные сразу.

Однако, как сказал Франк ван Пуффелен в комментариях выше, это не означает, что выборка 3 документов будет в 3 раза медленнее, чем выборка одного документа. Лучше всего провести свои собственные измерения, прежде чем делать вывод здесь.

Вот как вы могли бы сделать что-то подобное в Kotlin с Android SDK.
Не обязательно в одном цикле, но он эффективно группирует результат и позволяет избежать множества вложенных обратных вызовов.

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

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

Обратите внимание, что выборка определенных документов намного лучше, чем выборка всех документов и фильтрация результата. Это связано с тем, что Firestore взимает плату за набор результатов запроса.

На практике вы бы использовали 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 ())))
}

Для тех, кто хочет сделать это с помощью 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');
}

Вот метод выполнения запроса: ('product_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 в облачной функции? Тогда от клиента к Firebase будет только один звонок туда и обратно, что, похоже, именно то, о чем вы просите.

Вы действительно хотите сохранить всю логику доступа к данным, как на этой стороне сервера.

Внутри, вероятно, будет такое же количество вызовов самой Firebase, но все они будут осуществляться через сверхбыстрые межсоединения Google, а не через внешнюю сеть, и в сочетании с конвейерной обработкой, которую объяснил Фрэнк ван Пуффелен, вы должны получить отличная производительность при таком подходе.

Вы можете использовать такую ​​функцию:

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

It can be called with a single ID:

getById('collection', 'some_id')

или массив идентификаторов:

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

2022 WebDevInsider