database - data - firestore update collection




Come ottenere un conteggio del numero di documenti in una raccolta con Cloud Firestore (4)

In Firestore, come posso ottenere il numero totale di documenti in una raccolta?

Per esempio se ho

/people
    /123456
        /name - 'John'
    /456789
        /name - 'Jane'

Voglio interrogare quante persone ho e ottenere 2.

Potrei fare una query su / people e quindi ottenere la lunghezza dei risultati restituiti, ma questo sembra uno spreco, soprattutto perché lo farò su set di dati più grandi.


Al momento hai 3 opzioni:

Opzione 1: lato client

Questo è fondamentalmente l'approccio che hai citato. Seleziona tutto dalla raccolta e conta sul lato client. Funziona abbastanza bene per piccoli set di dati ma ovviamente non funziona se il set di dati è più grande.

Opzione 2: miglior tempo di scrittura

Con questo approccio, è possibile utilizzare le funzioni cloud per aggiornare un contatore per ogni aggiunta ed eliminazione dalla raccolta.

Funziona bene per qualsiasi dimensione del set di dati, purché le aggiunte / eliminazioni avvengano solo alla velocità inferiore o uguale a 1 al secondo. Questo ti dà un singolo documento da leggere per darti il ​​conteggio quasi attuale immediatamente.

Se è necessario superare 1 al secondo, è necessario implementare contatori distribuiti secondo la nostra documentazione .

Opzione 3: tempo di scrittura esatto

Anziché utilizzare le funzioni cloud, nel client è possibile aggiornare il contatore contemporaneamente all'aggiunta o all'eliminazione di un documento. Ciò significa che anche il contatore sarà aggiornato, ma dovrai assicurarti di includere questa logica ovunque aggiungi o elimini documenti.

Come per l'opzione 2, dovrai implementare contatori distribuiti se vuoi superare il secondo


Fai attenzione a contare il numero di documenti per raccolte di grandi dimensioni con una funzione cloud. È un po 'complesso con il database di firestore se si desidera avere un contatore precalcolato per ogni raccolta.

Codice come questo non funziona in questo caso:

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

Il motivo è perché ogni trigger di cloud store deve essere idempotente, come dice la documentazione di fireestore: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees

Soluzione

Pertanto, al fine di prevenire più esecuzioni del codice, è necessario gestire eventi e transazioni. Questo è il mio modo particolare di gestire i contatori di raccolte di grandi dimensioni:

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

Casi d'uso qui:

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

Come puoi vedere, la chiave per impedire l'esecuzione multipla è la proprietà chiamata eventId nell'oggetto contesto. Se la funzione è stata gestita più volte per lo stesso evento, l'ID evento sarà lo stesso in tutti i casi. Sfortunatamente, devi avere una raccolta di "eventi" nel tuo database.


Se usi AngulareFire2, puoi farlo (supponendo che private afs: AngularFirestore sia iniettato nel tuo costruttore):

db.collection('products').get().then(res => console.log(res.size))

Qui, values è un array di tutti gli elementi in myCollection . Non hai bisogno di metadati quindi puoi usare direttamente il metodo valueChanges() .


A seguito di Dan Risposta : È possibile avere un contatore separato nel database e utilizzare le funzioni cloud per mantenerlo. ( Miglior tempo di scrittura )

// Example of performing an increment when item is added
module.exports.incrementIncomesCounter = collectionRef.onCreate(event => {
  const counterRef = event.data.ref.firestore.doc('counters/incomes')

  counterRef.get()
  .then(documentSnapshot => {
    const currentCount = documentSnapshot.exists ? documentSnapshot.data().count : 0

    counterRef.set({
      count: Number(currentCount) + 1
    })
    .then(() => {
      console.log('counter has increased!')
    })
  })
})

Questo codice mostra l'esempio completo di come farlo: https://gist.github.com/saintplay/3f965e0aea933a1129cc2c9a823e74d7





google-cloud-firestore