对于 Firebase 中 “IN” 限制为 10 与 onSnapshot 兼容的解决方法?

huangapple go评论61阅读模式
英文:

Workaround for firebase “IN” limit of 10 compatible with onSnapshot?

问题

我已经达到了Firebase的"IN"查询限制,限制为10。虽然已经在这里提供了一个解决方法:

https://stackoverflow.com/questions/61354866/is-there-a-workaround-for-the-firebase-query-in-limit-to-10

但是,该主题中的解决方法似乎都不适用于"onSnapshot"监听器。对于我的用例(Vue 3),我有一个可组合/函数调用,它查询Firebase并传入一个最多包含100个文档ID值的数组,并返回如下的对象。

是否有可能实现这一点?

import { ref, watchEffect } from 'vue'
import { db } from '@/firebase/config'
import { collection, onSnapshot, query, where, documentId } from 'firebase/firestore'

const getUsersList = (idList) => {
    // idList是一个文档ID数组
    const documents = ref(null)
    let collectionRef = collection(db, 'users')
    collectionRef = query(collectionRef, where(documentId(), 'in', idList))
    // 如果我传入超过10个元素的数组,这将失败
    const unsub = onSnapshot(collectionRef, snapshot => {
        let results = []
        snapshot.docs.forEach(doc => {
            results.push({ ...doc.data(), id: doc.id })
        })
        // 更新数值
        documents.value = results
    })
    watchEffect(onInvalidate => {
        onInvalidate(() => unsub())
    })
    return { documents }
}

export default getUsersList

希望这有所帮助。

英文:

I have run into the firebase “IN” limit of 10. Although a workaround solution was already answered here:

https://stackoverflow.com/questions/61354866/is-there-a-workaround-for-the-firebase-query-in-limit-to-10

None of the solutions in that thread seem to work with the listener “onSnapshot”. For my use case (Vue 3), I have a composable/function call I that queries firebase passing in an array that could have up to 100 document ID values and returns an object as below.

Is this possible?

import { ref, watchEffect } from 'vue'
import { db } from '@/firebase/config'
import { collection, onSnapshot, query, where, documentId } from 'firebase/firestore'

const getUsersList = (idList) => { 
    // idList is an array of document ID's
    const documents = ref(null)
    let collectionRef = collection(db, 'users')
    collectionRef = query(collectionRef, where(documentId(), 'in', idList))
    // this fails if I pass in more than 10 elements in the array
    const unsub = onSnapshot(collectionRef, snapshot => {
        let results = []
        snapshot.docs.forEach(doc => {
            results.push({ ...doc.data(), id: doc.id })
        })
        // update values
        documents.value = results
    })
    watchEffect((onInvalidate) => {
        onInvalidate(() => unsub())
    })
    return { documents }
}

export default getCollectionRt

答案1

得分: 1

以下是翻译好的部分:

"Since no replies here completely answered the question, I ended up paying a freelancer to take a look and here's what they came up with. The solution does seem to have a random issue I am trying to sort out when the underlying changes, one of the records will disappear. It does work, is in scope of the original question and seems to have solved the problem."

"自从这里没有完全回答我的问题,我最终付了一名自由职业者来查看,并这是他们提出的解决方案。解决方案似乎存在一个我正在努力解决的随机问题,当底层发生变化时,会有一条记录消失。它确实有效,符合原问题的范围,并似乎解决了问题。"

"import { ref, watchEffect } from 'vue'
import { db } from '@/firebase/config'
import { collection, onSnapshot, query, where, documentId } from 'firebase/firestore'

const getUserList = (idList) => {
console.log('idList', idList)
let documents = ref(null)
let collectionRef = collection(db, 'users')

let unsub, unsubes = [], resultsList = [{}];
for (let i = 0; i < idList.length; i += 10) {
    let idList1 = idList.slice(i, i + 10); //console.log(idList1);
    let collectionRef1 = query(collectionRef, where(documentId(), 'in', idList1))
    unsub = onSnapshot(collectionRef1, snapshot => {
        let results = []
        snapshot.docs.forEach(doc => {
            results.push({ ...doc.data(), id: doc.id })
        })
        resultsList.splice(resultsList.length, 0, ...results);
        console.log('results', results)
        documents.value = results
    })
    unsubes.push(unsub);
}
watchEffect(onInvalidate => {
    onInvalidate(() => { unsubes.forEach(unsub => { unsub(); console.log("unsut", unsub); }) });
})
Promise.all(unsubes);
resultsList.shift(0);
console.log("docu", documents.value);
return { documents };

}

export default getUserList"
"import { ref, watchEffect } from 'vue'
import { db } from '@/firebase/config'
import { collection, onSnapshot, query, where, documentId } from 'firebase/firestore'

const getUserList = (idList) => {
console.log('idList', idList)
let documents = ref(null)
let collectionRef = collection(db, 'users')

let unsub, unsubes = [], resultsList = [{}];
for (let i = 0; i < idList.length; i += 10) {
    let idList1 = idList.slice(i, i + 10); //console.log(idList1);
    let collectionRef1 = query(collectionRef, where(documentId(), 'in', idList1))
    unsub = onSnapshot(collectionRef1, snapshot => {
        let results = []
        snapshot.docs.forEach(doc => {
            results.push({ ...doc.data(), id: doc.id })
        })
        resultsList.splice(resultsList.length, 0, ...results);
        console.log('results', results)
        documents.value = results
    })
    unsubes.push(unsub);
}
watchEffect(onInvalidate => {
    onInvalidate(() => { unsubes.forEach(unsub => { unsub(); console.log("unsut", unsub); }) });
})
Promise.all(unsubes);
resultsList.shift(0);
console.log("docu", documents.value);
return { documents };

}

export default getUserList"

英文:

Since no replies here completely answered the question, I ended up paying a freelancer to take a look and here's what they came up with. The solution does seem to have a random issue I am trying to sort out when the underlying changes, one of the records will disappear. It does work, is in scope of the original question and seems to have solved the problem.

import { ref, watchEffect } from &#39;vue&#39;
import { db } from &#39;@/firebase/config&#39;
import { collection, onSnapshot, query, where, documentId } from &#39;firebase/firestore&#39;


const getUserList = (idList) =&gt; {
    console.log(&#39;idList&#39;, idList)
    let documents = ref(null)
    let collectionRef = collection(db, &#39;users&#39;)

    let unsub, unsubes = [], resultsList = [{}];
    for (let i = 0; i &lt; idList.length; i += 10) {
        let idList1 = idList.slice(i, i + 10); //console.log(idList1);
        let collectionRef1 = query(collectionRef, where(documentId(), &#39;in&#39;, idList1))
        unsub = onSnapshot(collectionRef1, snapshot =&gt; {
            let results = []
            snapshot.docs.forEach(doc =&gt; {
                results.push({ ...doc.data(), id: doc.id })
            })
            resultsList.splice(resultsList.length, 0, ...results);
            console.log(&#39;results&#39;, results)
            documents.value = results
        })
        unsubes.push(unsub);
    }
    watchEffect((onInvalidate) =&gt; {
        onInvalidate(() =&gt;{ unsubes.forEach(unsub =&gt; { unsub();         console.log(&quot;unsut&quot;, unsub); }) });       
    })
    Promise.all(unsubes);
    resultsList.shift(0);
    console.log(&quot;docu&quot;, documents.value);
    return { documents };
}

export default getUserList

答案2

得分: 0

你将不得不初始化多个监听器,即相同数量的查询,但使用onSnapshot()(可能比为每个单独的文档设置一个监听器更好)。尝试:

import { ref } from 'vue';
import { collection, query, where, documentId, onSnapshot } from 'firebase/firestore';

const users = ref({});
const dataLoaded = ref(false);

const addFirestoreListeners = async () => {
  const idList = [];

  for (let i = 0; i < idList.length; i += 10) {
    const items = idList.slice(i, i + 10);

    const q = query(collection(db, 'users'), where(documentId(), 'in', items));

    onSnapshot(q, (snapshot) => {
      if (dataLoaded.value) {
        snapshot.docChanges().forEach((change) => {
          if (change.type === 'added' || change.type === 'modified') {
            users.value[change.doc.id] = change.doc.data();
          } else if (change.type === 'removed') {
            users.value[change.doc.id] = null;
          }
        });
      } else {
        snapshot.forEach((doc) => {
          users.value[doc.id] = doc.data();
        });

        dataLoaded.value = true;
      }
    });
  }
}

dataLoaded 标志检查监听器是否第一次获取数据或已接收更新。我使用了一个映射,其中键是文档 ID,以便可以轻松删除它,但可以将其更改为数组或任何其他所需的结构。

英文:

You will have to initialize multiple listeners i.e. same number of queries but with onSnapshot() (might be better than setting up a listener for each individual document). Try:

import { ref } from &#39;vue&#39;;
import { collection, query, where, documentId, onSnapshot } from &#39;firebase/firestore&#39;

const users = ref({})
const dataLoaded = ref(false)

const addFirestoreListeners = async () =&gt; {
  const idList = [];

  for (let i = 0; i &lt; idList.length; i += 10) {
    const items = idList.slice(i, i + 10)

    const q = query(collection(db, &#39;users&#39;), where(documentId(), &#39;in&#39;, items))

    onSnapshot(q, (snapshot) =&gt; {
      if (dataLoaded.value) {
        snapshot.docChanges().forEach((change) =&gt; {
          if (change.type === &#39;added&#39; || change.type === &#39;modified&#39;) {
            users.value[change.doc.id] = change.doc.data()
          } else (change.type === &#39;removed&#39;) {
            users.value[change.doc.id] = null
          }
        })
      } else {
        snapshot.forEach((doc) =&gt; {
          users.value[doc.id] = doc.data()
        })

        dataLoaded.value = true
      }
    })
  }
}

The dataLoaded flag checks if the listeners have fetched data for first time or has received an update. I've use a map where the key is document ID so it can be removed easily but do change that to an array or any other required structure.

huangapple
  • 本文由 发表于 2023年2月7日 03:51:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75365933.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定