Если у меня действительно большое количество документов в коллекции, могу ли я получить количество документов, не загружая всю коллекцию.
db.collection('largecollection').get().then(snapshot => {
length = snapshot.size;
})

Нет встроенного API для подсчета количества документов в коллекции. Так что по умолчанию вам придется загрузить все документы, чтобы их подсчитать.
Как прокомментировал Рено, обычным обходным путем для этого является сохранение отдельного счетчика, который вы обновляете каждый раз, когда документ добавляется / удаляется. Вы можете делать это из своего приложения, обновление счетчика транзакцией каждый раз, когда вы добавляете / удаляете документ. Но надежнее сделать это на сервере, например Cloud Functions for Firebase. Для примера этого подхода см. репо functions-samples. Хотя образец предназначен для базы данных Firebase Realtime, этот подход в равной степени применим и к Firestore.
Также см. Раздел документации Firestore на распределенные счетчики.
Будьте осторожны при подсчете количества документов для большие коллекции. Это немного сложно с базой данных firestore, если вы хотите иметь заранее рассчитанный счетчик для каждой коллекции.
Такой код в этом случае не работает:
export const customerCounterListener =
functions.firestore.document('customers/{customerId}')
.onWrite((change, context) => {
// on create
if (!change.before.exists && change.after.exists) {
return firestore
.collection('metadatas')
.doc('customers')
.get()
.then(docSnap =>
docSnap.ref.set({
count: docSnap.data().count + 1
}))
// on delete
} else if (change.before.exists && !change.after.exists) {
return firestore
.collection('metadatas')
.doc('customers')
.get()
.then(docSnap =>
docSnap.ref.set({
count: docSnap.data().count - 1
}))
}
return null;
});
Причина в том, что каждый триггер облачного хранилища должен быть идемпотентным, как сказано в документации хранилища: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees
Итак, чтобы предотвратить многократное выполнение вашего кода, вам нужно управлять событиями и транзакциями. Это мой особый способ работы со счетчиками больших коллекций:
const executeOnce = (change, context, task) => {
const eventRef = firestore.collection('events').doc(context.eventId);
return firestore.runTransaction(t =>
t
.get(eventRef)
.then(docSnap => (docSnap.exists ? null : task(t)))
.then(() => t.set(eventRef, { processed: true }))
);
};
const documentCounter = collectionName => (change, context) =>
executeOnce(change, context, t => {
// on create
if (!change.before.exists && change.after.exists) {
return t
.get(firestore.collection('metadatas')
.doc(collectionName))
.then(docSnap =>
t.set(docSnap.ref, {
count: ((docSnap.data() && docSnap.data().count) || 0) + 1
}));
// on delete
} else if (change.before.exists && !change.after.exists) {
return t
.get(firestore.collection('metadatas')
.doc(collectionName))
.then(docSnap =>
t.set(docSnap.ref, {
count: docSnap.data().count - 1
}));
}
return null;
});
Примеры использования здесь:
/**
* Count documents in articles collection.
*/
exports.articlesCounter = functions.firestore
.document('articles/{id}')
.onWrite(documentCounter('articles'));
/**
* Count documents in customers collection.
*/
exports.customersCounter = functions.firestore
.document('customers/{id}')
.onWrite(documentCounter('customers'));
Как видите, ключом к предотвращению множественного выполнения является свойство eventId в объекте контекста. Если функция обрабатывалась много раз для одного и того же события, идентификатор события будет одинаковым во всех случаях. К сожалению, в вашей базе данных должна быть коллекция «событий».
Вы можете использовать облачную функцию для обновления счетчика каждый раз, когда документ добавляется в коллекцию (а также каждый раз, когда он удаляется, если необходимо).