是否可以使用新的Firebase数据库Cloud Firestore来计算一个集合有多少项?

如果是,我该怎么做?


当前回答

据我所知,目前还没有内置的解决方案,只能在节点sdk中实现。 如果你有

db.collection('someCollection')

你可以使用

.select([fields])

定义要选择的字段。如果执行空select(),则只会得到一个文档引用数组。

例子:

db.collection (someCollection) .select () . get () ( (snapshot) => console.log(snapshot.docs.length) );

此解决方案只是针对下载所有文档的最坏情况的优化,并且不能扩展到大型集合!

再看看这个: 如何获得在一个集合与云Firestore的文件的数量计数

其他回答

有了新版本的Firebase,您现在可以运行聚合查询了! 简单的写

.count().get(); 

在您的询问之后。

firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });

和许多问题一样,答案是——视情况而定。

在前端处理大量数据时应该非常小心。除了让你的前端感觉迟钝之外,Firestore还会向你收取每百万次读取60美元的费用。


小型收藏(少于100份文件)

小心使用-前端用户体验可能会受到影响

在前端处理这个应该没问题,只要你没有对这个返回的数组做太多的逻辑处理。

db.collection('...').get().then(snap => {
  size = snap.size // will return the collection size
});

中等藏书(100至1000份)

小心使用- Firestore读取调用可能会花费很多

在前端处理这个问题是不可行的,因为它有很大的可能会降低用户系统的速度。我们应该处理这个逻辑服务器端,只返回大小。

这种方法的缺点是您仍然在调用Firestore读取(等于您的集合的大小),从长远来看,这最终可能会使您的成本超过预期。

云功能:

db.collection('...').get().then(snap => {
  res.status(200).send({length: snap.size});
});

前端:

yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
   size = snap.length // will return the collection size
})

大量的收集(1000+文档)

最具可扩展性的解决方案


FieldValue.increment ()

截至2019年4月,Firestore现在允许增量计数器,完全原子,无需事先读取数据。这确保了即使同时从多个源进行更新(以前使用事务解决)也能获得正确的计数器值,同时还减少了执行的数据库读取次数。


通过监听任何删除或创建的文档,我们可以向数据库中的计数字段添加或删除。

参见firestore文档-分布式计数器 或者看看杰夫·德莱尼的《数据聚合》。他的指南对于任何使用AngularFire的人来说都是非常棒的,但他的课程也应该适用于其他框架。

云功能:

export const documentWriteListener = functions.firestore
  .document('collection/{documentUid}')
  .onWrite((change, context) => {

    if (!change.before.exists) {
      // New document Created : add one to count
      db.doc(docRef).update({ numberOfDocs: FieldValue.increment(1) });
    } else if (change.before.exists && change.after.exists) {
      // Updating existing document : Do nothing
    } else if (!change.after.exists) {
      // Deleting document : subtract one from count
      db.doc(docRef).update({ numberOfDocs: FieldValue.increment(-1) });
    }

    return;
  });

现在在前端,你可以查询这个numberOfDocs字段来获得集合的大小。

Firebase/Firestore中的新功能提供了集合中的文档计数:

请参阅本线程以了解如何实现它,并提供了一个示例。

如何计算在一个集合中的Firebase Firestore与WHERE查询在react.js文档的数量

对于大量的收藏,要仔细计算文件的数量。如果你想为每个收集都有一个预先计算好的计数器,那么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的属性。如果函数对同一个事件处理了多次,那么事件id在所有情况下都是相同的。不幸的是,您的数据库中必须有“事件”集合。