Cloud Firestore collection count

后端 未结 17 1531
庸人自扰
庸人自扰 2020-11-22 09:25

Is it possible to count how many items a collection has using the new Firebase database, Cloud Firestore?

If so, how do I do that?

17条回答
  •  迷失自我
    2020-11-22 09:41

    Be careful counting number of documents for large collections. It is a little bit complex with firestore database if you want to have a precalculated counter for every collection.

    Code like this doesn't work in this case:

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

    The reason is because every cloud firestore trigger has to be idempotent, as firestore documentation say: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

    Solution

    So, in order to prevent multiple executions of your code, you need to manage with events and transactions. This is my particular way to handle large collection counters:

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

    Use cases here:

    /**
     * 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'));
    

    As you can see, the key to prevent multiple execution is the property called eventId in the context object. If the function has been handled many times for the same event, the event id will be the same in all cases. Unfortunately, you must have "events" collection in your database.

提交回复
热议问题