返回
Firestore 多查询结果聚合与实时更新解决方案
javascript
2024-12-14 15:21:05
Firestore 多查询结果聚合与实时更新
在 Firestore 应用开发中,有时需要将多个查询的结果合并成一个数据集,并且保持对数据库的实时更新同步。当查询条件受限于 Firestore 的特定规则(例如 in
查询的文档数量限制),或者需要合并来自不同查询的数据时,这个问题就会变得更加复杂。本文将探讨如何解决这个问题,并提供可行的解决方案。
问题分析
当单个 Firestore 查询无法满足需求时,需要将多个查询组合起来。Firestore 的 in
查询有 30 个文档的限制,当需要根据 ID 查询大量文档时,必须将查询分批进行。而 vector search
功能依赖 Firebase Function,返回的数据也需要通过 ID 重新查询 Firestore 文档,这进一步加剧了问题复杂性。同时,保持与 Firestore 的实时数据同步也至关重要。我们需要找到一种方法,既能处理多个查询,又能将结果合并,并且能够响应数据库的实时更新。
解决方案
1. 客户端手动合并查询结果并监听
此方案通过在客户端手动处理多个查询,并将结果合并到一个响应式数组中。该方案利用 onSnapshot
监听每个查询的数据变化,并通过计算文档在数组中的偏移量来更新数组,以保证数据的实时性和一致性。
原理:
- 将 ID 数组分批,每批不超过 Firestore 的
in
查询限制(通常为 30)。 - 为每批 ID 创建一个 Firestore 查询。
- 使用
onSnapshot
监听每个查询的数据变化。 - 在
onSnapshot
的回调函数中,根据文档变化类型(添加、修改、删除)更新聚合数组。添加时,计算文档的偏移量并插入数组;修改时,先移除旧文档再插入新文档;删除时,直接移除文档。
步骤:
-
将 ID 数组分批:
function slices(array: any[], size: number) { const result = []; for (let i = 0; i < array.length; i += size) { result.push(array.slice(i, i + size)); } return result; }
-
创建多个 Firestore 查询:
import { db } from './firebase' import { collection, query, where, documentId, Query, QuerySnapshot, DocumentData } from 'firebase/firestore' const FIRESTORE_ID_QUERY_LIMIT = 30 export function queryDocumentsByIds(collectionPath: string, ids: string[]): Query[] { const queries: Query[] = [] const batches = slices(ids, FIRESTORE_ID_QUERY_LIMIT) for (const batch of batches) { const q = query(collection(db, collectionPath), where(documentId(), 'in', batch)) queries.push(q) } return queries }
-
监听多个查询并合并结果:
import { onSnapshot } from 'firebase/firestore' import { ref, Ref } from 'vue' function onErrorHandler(error:any){ console.error("An error occurred with the query subscription: ", error); } export function useQueryArray(collectionPath: string, ids: string[]): Ref<any[]> { const multiRef = ref([]) as Ref<any[]> const queries: Query[] = queryDocumentsByIds(collectionPath, ids) for (let i = 0; i < queries.length; i++) { const q = queries[i] const offset = i * FIRESTORE_ID_QUERY_LIMIT onSnapshot(q, (snapshot: QuerySnapshot<DocumentData, DocumentData>) => { snapshot.docChanges().forEach(change => { const { oldIndex, newIndex, doc, type } = change if(newIndex == -1) return; const actualIndex = offset + newIndex if (type === 'added') { multiRef.value.splice(actualIndex, 0, {id: doc.id, ...doc.data()}) } else if (type === 'modified') { const indexToRemove = oldIndex === -1 ? actualIndex : offset + oldIndex multiRef.value.splice(indexToRemove, 1) multiRef.value.splice(actualIndex, 0, {id: doc.id, ...doc.data()}) } else if (type === 'removed') { const indexToRemove = oldIndex === -1 ? actualIndex : offset + oldIndex multiRef.value.splice(indexToRemove, 1) } }) }, onErrorHandler) } return multiRef }
示例代码:
```vue