Firebase 云Firestore收集计数
是否可以使用新的Firebase数据库Cloud Firestore计算集合中有多少项Firebase 云Firestore收集计数,firebase,google-cloud-firestore,Firebase,Google Cloud Firestore,是否可以使用新的Firebase数据库Cloud Firestore计算集合中有多少项 如果是这样的话,我该怎么做?据我所知,目前没有内置解决方案,只能在node sdk中使用。 如果你有 db.collection('someCollection') 你可以用 .select([fields]) 定义要选择的字段。如果执行空select(),则只会得到一个文档引用数组 例如: db.collection('someCollection')。选择()。获取()。然后( (快照)=>conso
如果是这样的话,我该怎么做?据我所知,目前没有内置解决方案,只能在node sdk中使用。 如果你有
db.collection('someCollection')
你可以用
.select([fields])
定义要选择的字段。如果执行空select(),则只会得到一个文档引用数组
例如:
db.collection('someCollection')。选择()。获取()。然后(
(快照)=>console.log(snapshot.docs.length)
);代码>
此解决方案仅针对下载所有文档的最坏情况进行了优化,不会扩展到大型集合
还可以看看这个:
否,目前没有对聚合查询的内置支持。然而,你可以做一些事情
第一个是。您可以使用事务或云功能来维护聚合信息:
此示例演示如何使用函数跟踪子集合中的评分数以及平均评分
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中可用,因此解决方案在移动应用程序中不起作用。最简单的方法是读取“querySnapshot”的大小
您还可以读取“querySnapshot”中docs数组的长度
或者,如果通过读取空值“querySnapshot”为空,则返回布尔值
querySnapshot.empty;
正如许多问题一样,答案是——这要看情况而定
在前端处理大量数据时,应该非常小心。除了让前端感觉迟钝之外,您还可以制作Firestore。
小型收藏(少于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
})
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;
});
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;
});
前端:
...
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
})
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;
});
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;
});
大型收藏(1000多份文档)
最具扩展性的解决方案
FieldValue.increment()
这确保了即使同时从多个源进行更新(以前使用事务解决),我们也能获得正确的计数器值,同时还减少了我们执行的数据库读取次数
通过监听任何文档的删除或创建,我们可以添加或删除数据库中的计数字段
请参阅firestore文档-
或者看看杰夫·德莱尼的作品。对于任何使用AngularFire的人来说,他的指南都是非常棒的,但是他的课程也应该延续到其他框架中
云功能:
...
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
})
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;
});
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;
});
现在,在前端,您只需查询numberOfDocs字段即可获得集合的大小。firebaseFirestore.collection(“…”).addSnapshotListener(new EventListener(){
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
int Counter = documentSnapshots.size();
}
});
@凌驾
public void OneEvent(QuerySnapshot文档快照,FireBaseFireStore异常e){
int Counter=documentSnapshots.size();
}
});
我同意@Matthew的观点,如果您执行这样的查询,将花费大量的成本
[开始项目前给开发人员的建议]
由于我们在一开始就预见到了这种情况,我们实际上可以创建一个集合,即带有文档的计数器,以将所有计数器存储在类型为number
的字段中
例如:
对于集合上的每个CRUD操作,更新计数器文档:
创建新集合/子集合时:(+1在计数器中)[1写入操作]
当您删除一个集合/子集合时:(-1在计数器中)[1写入操作]
更新现有集合/子集合时,不对计数器文档执行任何操作:(0)
阅读现有集合/子集合时,对计数器文档不执行任何操作:(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'}
因此,如果不打算删除集合,实际上可以使用数组来存储集合名称列表,而不是每次都查询所有集合
希望有帮助 仔细计算大型集合的文档数量。如果您希望为每个集合都有一个预先计算的计数器,那么firestore数据库有点复杂
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
@Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
int Counter = documentSnapshots.size();
}
});
这种代码在这种情况下不起作用:
...
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
})
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;
});
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触发器都必须是幂等的:
解决方案
因此,为了防止代码多次执行,您需要使用事件和事务进行管理。这是我处理大型收集计数器的特殊方式:
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;
});
这里的用例