261

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?

4

20 回答 20

302

与许多问题一样,答案是 - 视情况而

在前端处理大量数据时应该非常小心。除了让您的前端感觉迟钝之外,Firestore 还向您收取每百万次阅读 0.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 文档 - Distributed Counters 或查看 Jeff Delaney 的Data Aggregation。他的指南对于任何使用 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 字段来获取集合的大小。

于 2018-03-21T13:25:53.183 回答
38

最简单的方法是读取“querySnapshot”的大小。

db.collection("cities").get().then(function(querySnapshot) {      
    console.log(querySnapshot.size); 
});

您还可以在“querySnapshot”中读取 docs 数组的长度。

querySnapshot.docs.length;

或者,如果“querySnapshot”通过读取空值为空,则将返回一个布尔值。

querySnapshot.empty;
于 2017-10-13T17:08:12.690 回答
32

据我所知,没有内置解决方案,目前只能在节点 sdk 中使用。如果你有一个

db.collection('someCollection')

您可以使用

.select([fields])

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

例子:

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

此解决方案仅针对下载所有文档的最坏情况进行了优化,并且不适用于大型集合!

也看看这个:
How to get a count of documents in a collection with Cloud Firestore

于 2017-10-03T22:39:20.570 回答
18

小心计算大量收藏的文档数量。如果您想为每个集合设置一个预先计算的计数器,那么使用 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;
});

原因是因为每个云 Firestore 触发器都必须是幂等的,正如 Firestore 文档所说: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 在所有情况下都相同。不幸的是,您的数据库中必须有“事件”集合。

于 2018-10-22T09:50:20.040 回答
17

在 2020 年,Firebase SDK 中仍不提供此功能,但Firebase Extensions (Beta)中提供了此功能,但设置和使用非常复杂...

合理的做法

助手...(创建/删除似乎是多余的,但比 onUpdate 便宜)

export const onCreateCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(1);
  await statsDoc.set(countDoc, { merge: true });
};

export const onDeleteCounter = () => async (
  change,
  context
) => {
  const collectionPath = change.ref.parent.path;
  const statsDoc = db.doc("counters/" + collectionPath);
  const countDoc = {};
  countDoc["count"] = admin.firestore.FieldValue.increment(-1);
  await statsDoc.set(countDoc, { merge: true });
};

export interface CounterPath {
  watch: string;
  name: string;
}

导出的 Firestore 挂钩


export const Counters: CounterPath[] = [
  {
    name: "count_buildings",
    watch: "buildings/{id2}"
  },
  {
    name: "count_buildings_subcollections",
    watch: "buildings/{id2}/{id3}/{id4}"
  }
];


Counters.forEach(item => {
  exports[item.name + '_create'] = functions.firestore
    .document(item.watch)
    .onCreate(onCreateCounter());

  exports[item.name + '_delete'] = functions.firestore
    .document(item.watch)
    .onDelete(onDeleteCounter());
});

在行动

将跟踪构建集合和所有子集合。

在此处输入图像描述

这里在/counters/根路径下

在此处输入图像描述

现在收集计数将自动更新,最终!如果您需要计数,只需使用集合路径并在其前面加上counters.

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
  .doc('counters/' + collectionPath)
  .get()
  .then(snap => snap.get('count'));

限制

由于此方法使用单个数据库和文档,因此它仅限于 Firestore 约束,即每个计数器每秒更新 1 次。它将最终保持一致,但在添加/删除大量文档的情况下,计数器将落后于实际收集计数。

于 2019-12-03T06:01:56.000 回答
10

我同意@Matthew,如果你执行这样的查询 会花费很多。

[在开始项目之前给开发者的建议]

由于我们一开始就预见到了这种情况,我们实际上可以用一个文档制作一个集合,即计数器,将所有计数器存储在一个类型为 的字段中number

例如:

对于集合上的每个 CRUD 操作,更新计数器文档:

  1. 当您创建新的集合/子集合时:(计数器中的+1) [1 次写入操作]
  2. 当你删除一个集合/子集合时:(计数器中的-1) [1个写操作]
  3. 当您更新现有集合/子集合时,对计数器文档不执行任何操作:(0)
  4. 当您阅读现有的集合/子集合时,对计数器文档不执行任何操作:(0)

下次想要获取集合的数量时,只需要查询/指向文档字段即可。[1次读取操作]

另外,您可以将集合名称存储在数组中,但这会很棘手,firebase中数组的条件如下所示:

// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}

// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']

// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}

因此,如果您不打算删除集合,您实际上可以使用数组来存储集合名称列表,而不是每次都查询所有集合。

希望能帮助到你!

于 2018-09-14T07:07:03.123 回答
6

使用admin.firestore.FieldValue.increment增加一个计数器:

exports.onInstanceCreate = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onCreate((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(1),
    })
  );

exports.onInstanceDelete = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onDelete((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(-1),
    })
  );

在此示例instanceCount中,每次将文档添加到instances子集合时,我们都会在项目中增加一个字段。如果该字段尚不存在,它将被创建并递增到 1。

增量在内部是事务性的,但如果您需要比每 1 秒更频繁地增加,您应该使用分布式计数器。

它通常更可取onCreateonDelete而不是onWrite像您要求onWrite更新那样,这意味着您在不必要的函数调用上花费了更多的钱(如果您更新集合中的文档)。

于 2019-04-06T18:11:52.863 回答
5

不,目前没有对聚合查询的内置支持。但是,您可以做一些事情。

第一个记录在这里。您可以使用事务或云功能来维护汇总信息:

此示例说明如何使用函数来跟踪子集合中的评分数以及平均评分。

exports.aggregateRatings = firestore
  .document('restaurants/{restId}/ratings/{ratingId}')
  .onWrite(event => {
    // Get value of the newly added rating
    var ratingVal = event.data.get('rating');

    // Get a reference to the restaurant
    var restRef = db.collection('restaurants').document(event.params.restId);

    // Update aggregations in a transaction
    return db.transaction(transaction => {
      return transaction.get(restRef).then(restDoc => {
        // Compute new number of ratings
        var newNumRatings = restDoc.data('numRatings') + 1;

        // Compute new average rating
        var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
        var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;

        // Update restaurant info
        return transaction.update(restRef, {
          avgRating: newAvgRating,
          numRatings: newNumRatings
        });
      });
    });
});

如果您只想不频繁地计算文档,jbb 提到的解决方案也很有用。确保使用该select()语句来避免下载每个文档的所有内容(当您只需要计数时,这会占用大量带宽)。 select()目前仅在服务器 SDK 中可用,因此该解决方案不适用于移动应用程序。

于 2017-10-03T23:43:51.990 回答
5

没有可用的直接选项。你不能这样做db.collection("CollectionName").count()。以下是查找集合中文档数的两种方法。

1 :- 获取集合中的所有文档,然后获取它的大小。(不是最好的解决方案)

db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})

通过使用上述代码,您的文档读取将等于集合中文档的大小,这就是必须避免使用上述解决方案的原因。

2:- 在您的集合中创建一个单独的文档,该文档将存储集合中文档的数量。(最佳解决方案)

db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})

上面我们创建了一个包含名称计数的文档来存储所有计数信息。您可以通过以下方式更新计数文档:-

  • 在文档计数上创建一个 Firestore 触发器
  • 创建新文档时增加计数文档的计数属性。
  • 删除文档时减少计数文档的计数属性。

wrt price(Document Read = 1)和快速数据检索上述解决方案很好。

于 2019-04-30T13:33:08.940 回答
4

更新 11/20

我创建了一个 npm 包,以便轻松访问计数器功能:https ://fireblog.io/post/Zebl6sSbaLdrnSFKbCJx/firestore-counters


我使用所有这些想法创建了一个通用函数来处理所有计数器情况(查询除外)。

唯一的例外是当一秒钟写这么多时,它会减慢你的速度。一个例子是在热门帖子上点赞例如,在博客文章中使用它是多余的,并且会花费更多。我建议在这种情况下使用分片创建一个单独的函数: https ://firebase.google.com/docs/firestore/solutions/counters

// trigger collections
exports.myFunction = functions.firestore
    .document('{colId}/{docId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// trigger sub-collections
exports.mySubFunction = functions.firestore
    .document('{colId}/{docId}/{subColId}/{subDocId}')
    .onWrite(async (change: any, context: any) => {
        return runCounter(change, context);
    });

// add change the count
const runCounter = async function (change: any, context: any) {

    const col = context.params.colId;

    const eventsDoc = '_events';
    const countersDoc = '_counters';

    // ignore helper collections
    if (col.startsWith('_')) {
        return null;
    }
    // simplify event types
    const createDoc = change.after.exists && !change.before.exists;
    const updateDoc = change.before.exists && change.after.exists;

    if (updateDoc) {
        return null;
    }
    // check for sub collection
    const isSubCol = context.params.subDocId;

    const parentDoc = `${countersDoc}/${context.params.colId}`;
    const countDoc = isSubCol
        ? `${parentDoc}/${context.params.docId}/${context.params.subColId}`
        : `${parentDoc}`;

    // collection references
    const countRef = db.doc(countDoc);
    const countSnap = await countRef.get();

    // increment size if doc exists
    if (countSnap.exists) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.update(countRef, { count: i });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        const colRef = db.collection(change.after.ref.parent.path);
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(colRef);
            return t.set(countRef, { count: colSnap.size });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}

这处理事件、增量和事务。这样做的好处是,如果您不确定文档的准确性(可能仍处于测试阶段),您可以删除计数器,让它在下一次触发时自动添加它们。是的,这是成本,所以不要删除它。

同样的事情来获得计数:

const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');

此外,您可能希望创建一个 cron 作业(计划函数)来删除旧事件以节省数据库存储费用。您至少需要一个 blaze 计划,并且可能需要更多配置。例如,您可以在每周日晚上 11 点运行它。 https://firebase.google.com/docs/functions/schedule-functions

这是未经测试的,但应该进行一些调整:

exports.scheduledFunctionCrontab = functions.pubsub.schedule('5 11 * * *')
    .timeZone('America/New_York')
    .onRun(async (context) => {

        // get yesterday
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);

        const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
        const eventFilterSnap = await eventFilter.get();
        eventFilterSnap.forEach(async (doc: any) => {
            await doc.ref.delete();
        });
        return null;
    });

最后,不要忘记保护firestore.rules中的集合:

match /_counters/{document} {
  allow read;
  allow write: if false;
}
match /_events/{document} {
  allow read, write: if false;
}

更新:查询

如果您还想自动化查询计数,请添加到我的其他答案中,您可以在云函数中使用此修改后的代码:

    if (col === 'posts') {

        // counter reference - user doc ref
        const userRef = after ? after.userDoc : before.userDoc;
        // query reference
        const postsQuery = db.collection('posts').where('userDoc', "==", userRef);
        // add the count - postsCount on userDoc
        await addCount(change, context, postsQuery, userRef, 'postsCount');

    }
    return delEvents();

这将自动更新userDocument中的 postsCount。您可以通过这种方式轻松地将其他计数添加到多个计数中。这只是让您了解如何使事情自动化。我还为您提供了另一种删除事件的方法。您必须阅读每个日期才能将其删除,因此以后删除它们并不能真正节省您的时间,只会使功能变慢。

/**
 * Adds a counter to a doc
 * @param change - change ref
 * @param context - context ref
 * @param queryRef - the query ref to count
 * @param countRef - the counter document ref
 * @param countName - the name of the counter on the counter document
 */
const addCount = async function (change: any, context: any, 
  queryRef: any, countRef: any, countName: string) {

    // events collection
    const eventsDoc = '_events';

    // simplify event type
    const createDoc = change.after.exists && !change.before.exists;

    // doc references
    const countSnap = await countRef.get();

    // increment size if field exists
    if (countSnap.get(countName)) {
        // createDoc or deleteDoc
        const n = createDoc ? 1 : -1;
        const i = admin.firestore.FieldValue.increment(n);

        // create event for accurate increment
        const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);

        return db.runTransaction(async (t: any): Promise<any> => {
            const eventSnap = await t.get(eventRef);
            // do nothing if event exists
            if (eventSnap.exists) {
                return null;
            }
            // add event and update size
            await t.set(countRef, { [countName]: i }, { merge: true });
            return t.set(eventRef, {
                completed: admin.firestore.FieldValue.serverTimestamp()
            });
        }).catch((e: any) => {
            console.log(e);
        });
        // otherwise count all docs in the collection and add size
    } else {
        return db.runTransaction(async (t: any): Promise<any> => {
            // update size
            const colSnap = await t.get(queryRef);
            return t.set(countRef, { [countName]: colSnap.size }, { merge: true });
        }).catch((e: any) => {
            console.log(e);
        });;
    }
}
/**
 * Deletes events over a day old
 */
const delEvents = async function () {

    // get yesterday
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);

    const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
    const eventFilterSnap = await eventFilter.get();
    eventFilterSnap.forEach(async (doc: any) => {
        await doc.ref.delete();
    });
    return null;
}

我还应该警告您,通用函数将在每个 onWrite 调用期间运行。仅在特定集合的 onCreate 和 onDelete 实例上运行该函数可能更便宜。就像我们正在使用的 noSQL 数据库一样,重复的代码和数据可以为您省钱。

于 2020-04-05T22:57:28.277 回答
3

一种解决方法是:

在 Firebase 文档中编写一个计数器,每次创建新条目时都会在事务中递增该计数器

您将计数存储在新条目的字段中(即:位置:4)。

然后在该字段上创建一个索引(位置 DESC)。

您可以使用 query.Where("position", "<" x).OrderBy("position", DESC)

希望这可以帮助!

于 2019-11-11T20:53:40.537 回答
2

我尝试了很多不同的方法。最后,我改进了其中一种方法。首先,您需要创建一个单独的集合并将所有事件保存在那里。其次,您需要创建一个由时间触发的新 lambda。此 lambda 将统计事件集合中的事件并清除事件文档。文章中的代码详细信息。 https://medium.com/@ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca

于 2020-05-27T15:58:51.683 回答
1

使用offset&分页的解决方案limit

public int collectionCount(String collection) {
        Integer page = 0;
        List<QueryDocumentSnapshot> snaps = new ArrayList<>();
        findDocsByPage(collection, page, snaps);
        return snaps.size();
    }

public void findDocsByPage(String collection, Integer page, 
                           List<QueryDocumentSnapshot> snaps) {
    try {
        Integer limit = 26000;
        FieldPath[] selectedFields = new FieldPath[] { FieldPath.of("id") };
        List<QueryDocumentSnapshot> snapshotPage;
        snapshotPage = fireStore()
                        .collection(collection)
                        .select(selectedFields)
                        .offset(page * limit)
                        .limit(limit)
                        .get().get().getDocuments();    
        if (snapshotPage.size() > 0) {
            snaps.addAll(snapshotPage);
            page++;
            findDocsByPage(collection, page, snaps);
        }
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}
  • findDocsPage这是一种查找所有集合页面的递归方法

  • selectedFields用于优化查询并仅获取 id 字段而不是完整的文档正文

  • limit每个查询页面的最大大小

  • page定义分页的初始页面

从我所做的测试来看,它适用于大约120k 记录的集合!

于 2020-10-23T21:50:32.550 回答
0

根据上面的一些答案,我花了一些时间来完成这项工作,所以我想我会分享它供其他人使用。我希望它有用。

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

exports.countDocumentsChange = functions.firestore.document('library/{categoryId}/documents/{documentId}').onWrite((change, context) => {

    const categoryId = context.params.categoryId;
    const categoryRef = db.collection('library').doc(categoryId)
    let FieldValue = require('firebase-admin').firestore.FieldValue;

    if (!change.before.exists) {

        // new document created : add one to count
        categoryRef.update({numberOfDocs: FieldValue.increment(1)});
        console.log("%s numberOfDocs incremented by 1", categoryId);

    } else if (change.before.exists && change.after.exists) {

        // updating existing document : Do nothing

    } else if (!change.after.exists) {

        // deleting document : subtract one from count
        categoryRef.update({numberOfDocs: FieldValue.increment(-1)});
        console.log("%s numberOfDocs decremented by 1", categoryId);

    }

    return 0;
});
于 2019-06-29T23:10:45.003 回答
0

快速+省钱的技巧之一是:-

创建一个doc并存储一个“计数”变量firestore,当用户在集合中添加新文档时,增加该变量,当用户删除文档时,减少变量。例如 updateDoc(doc(db, "Count_collection", "Count_Doc"), {count: increment(1)});

注意:使用 (-1) 表示减少,(1) 表示增加count

它如何节省金钱和时间:-

  1. 你(firebase)不需要遍历集合,浏览器也不需要加载整个集合来计算文档的数量。
  2. 所有计数都保存在只有一个名为“count”或其他变量的文档中,因此使用的数据少于 1kb,并且它在 firebase firestore 中仅使用 1 次读取。
于 2021-10-02T05:00:53.257 回答
0
var variable=0
variable=variable+querySnapshot.count

那么如果你要在 String 变量上使用它,那么

let stringVariable= String(variable)
于 2020-09-18T21:09:24.170 回答
0

除了我adv-firestore-functions上面的 npm 包,你还可以使用 firestore 规则来强制一个好的计数器:

Firestore 规则

function counter() {
  let docPath = /databases/$(database)/documents/_counters/$(request.path[3]);
  let afterCount = getAfter(docPath).data.count;
  let beforeCount = get(docPath).data.count;
  let addCount = afterCount == beforeCount + 1;
  let subCount = afterCount == beforeCount - 1;
  let newId = getAfter(docPath).data.docId == request.path[4];
  let deleteDoc = request.method == 'delete';
  let createDoc = request.method == 'create';
  return (newId && subCount && deleteDoc) || (newId && addCount && createDoc);
}

function counterDoc() {
  let doc = request.path[4];
  let docId = request.resource.data.docId;
  let afterCount = request.resource.data.count;
  let beforeCount = resource.data.count;
  let docPath = /databases/$(database)/documents/$(doc)/$(docId);
  let createIdDoc = existsAfter(docPath) && !exists(docPath);
  let deleteIdDoc = !existsAfter(docPath) && exists(docPath);
  let addCount = afterCount == beforeCount + 1;
  let subCount = afterCount == beforeCount - 1;
  return (createIdDoc && addCount) || (deleteIdDoc && subCount);
}

并像这样使用它们:

match /posts/{document} {
  allow read;
  allow update;
  allow create: if counter();
  allow delete: if counter();
}
match /_counters/{document} {
  allow read;
  allow write: if counterDoc();
}

前端

用这些替换您的设置和删除功能:

async setDocWithCounter(
  ref: DocumentReference<DocumentData>,
  data: {
    [x: string]: any;
  },
  options: SetOptions): Promise<void> {

  // counter collection
  const counterCol = '_counters';

  const col = ref.path.split('/').slice(0, -1).join('/');
  const countRef = doc(this.afs, counterCol, col);
  const countSnap = await getDoc(countRef);
  const refSnap = await getDoc(ref);

  // don't increase count if edit
  if (refSnap.exists()) {
    await setDoc(ref, data, options);

    // increase count
  } else {
    const batch = writeBatch(this.afs);
    batch.set(ref, data, options);

    // if count exists
    if (countSnap.exists()) {
      batch.update(countRef, {
        count: increment(1),
        docId: ref.id
      });
      // create count
    } else {
      // will only run once, should not use
      // for mature apps
      const colRef = collection(this.afs, col);
      const colSnap = await getDocs(colRef);
      batch.set(countRef, {
        count: colSnap.size + 1,
        docId: ref.id
      });
    }
    batch.commit();
  }
}

删除

async delWithCounter(
  ref: DocumentReference<DocumentData>
): Promise<void> {

  // counter collection
  const counterCol = '_counters';

  const col = ref.path.split('/').slice(0, -1).join('/');
  const countRef = doc(this.afs, counterCol, col);
  const countSnap = await getDoc(countRef);
  const batch = writeBatch(this.afs);

  // if count exists
  batch.delete(ref);
  if (countSnap.exists()) {
    batch.update(countRef, {
      count: increment(-1),
      docId: ref.id
    });
  }
  /*
  if ((countSnap.data() as any).count == 1) {
    batch.delete(countRef);
  }*/
  batch.commit();
}

请参阅此处了解更多信息...

Ĵ

于 2020-06-14T03:36:58.500 回答
0

这使用计数来创建数字唯一 ID。在我的使用中,我永远不会递减,即使document需要的 ID 被删除。

collection需要唯一数值的创作时

  1. appData用一个文档指定一个集合,set带有.docidonly
  2. 中设置uniqueNumericIDAmount为 0firebase firestore console
  3. 用作doc.data().uniqueNumericIDAmount + 1唯一的数字 id
  4. 更新appData集合uniqueNumericIDAmount_firebase.firestore.FieldValue.increment(1)
firebase
    .firestore()
    .collection("appData")
    .doc("only")
    .get()
    .then(doc => {
        var foo = doc.data();
        foo.id = doc.id;

        // your collection that needs a unique ID
        firebase
            .firestore()
            .collection("uniqueNumericIDs")
            .doc(user.uid)// user id in my case
            .set({// I use this in login, so this document doesn't
                  // exist yet, otherwise use update instead of set
                phone: this.state.phone,// whatever else you need
                uniqueNumericID: foo.uniqueNumericIDAmount + 1
            })
            .then(() => {

                // upon success of new ID, increment uniqueNumericIDAmount
                firebase
                    .firestore()
                    .collection("appData")
                    .doc("only")
                    .update({
                        uniqueNumericIDAmount: firebase.firestore.FieldValue.increment(
                            1
                        )
                    })
                    .catch(err => {
                        console.log(err);
                    });
            })
            .catch(err => {
                console.log(err);
            });
    });
于 2020-07-28T17:05:49.330 回答
-2
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            int Counter = documentSnapshots.size();

        }
    });
于 2018-06-01T16:59:36.547 回答
-8

所以我对这个问题的解决方案有点非技术性,不是超级精确,但对我来说已经足够好了。

在此处输入图像描述

这些是我的文件。因为我有很多(100k+),所以发生了“大数定律”。我可以假设有或多或少相等数量的 id 以 0、1、2 等开头的项目。

所以我要做的是滚动我的列表,直到我进入从 1 或 01 开始的 id,这取决于你必须滚动多长时间

在此处输入图像描述

我们到了。

现在,滚动到目前为止,我打开检查器并查看我滚动了多少并将其除以单个元素的高度

在此处输入图像描述

必须滚动 82000px 才能获取 id 以1. 单个元素的高度为 32px。

这意味着我有 2500 个 id 以 开头0,所以现在我将它乘以可能的“起始字符”的数量。在 firebase 中,它可以是 AZ、az、0-9,这意味着它是 24 + 24 + 10 = 58。

这意味着我有 ~~2500*58 所以它在我的收藏中提供了大约 145000 件物品。

总结:你的火力基地有什么问题?

于 2021-04-07T09:15:20.897 回答