云 Firestore:如何在我的集合查询中获取文档引用并将其映射为 JSON 值?

36

假设我有一个评论集合。每个评论对象都有一个指向发帖用户的“文档引用”。我需要查询返回一个评论列表,其中包括每个用户引用的值,以便我的查询返回格式良好的Json评论对象。

6个回答

31

这里曾经有一个类似的问题被问到Firebase Firestore参考数据类型有什么用处?,根据这个答案https://dev59.com/-lYN5IYBdhLWcg3w-sl-#46570119,我认为你所要求的不可能实现。

你需要自己加载每个引用,例如:

const comments = []
firebase.firestore().collection('/comments').get().then(snapshot => {
  snapshot.docs.forEach(doc => {
    const comment = doc.data()
    comment.userRef.get().then(snap => {
      comment.user = snap.data()
      comments.push(comment)
    })
  })
})

对于许多评论来说,这将增加很多开销。也许您可以编写一个云函数,在服务器端完成所有工作并返回格式化的JSON。

不过看起来他们可能正在努力支持这一点: https://dev59.com/MlYN5IYBdhLWcg3w9Mbr#46614683


10

这也让我非常恼火。我创建了一个实用程序帮助器,可以自动填充第一层。

帮助器(Helper)

import { get, set } from 'lodash'

const getReference = async documentReference => {
  const res = await documentReference.get()
  const data = res.data()

  if (data && documentReference.id) {
    data.uid = documentReference.id
  }

  return data
}

const hydrate = async (document, paths = []) => Promise.all(
  paths.map(async path => {
    const documentReference = get(document, path)

    if (!documentReference || !documentReference.path) {
      return console.warn(
        `Error hydrating documentReference for path "${path}": Not found or invalid reference`
      )
    }

    const result = await getReference(documentReference)
    set(document, path, result)
  })
)

export { hydrate }

使用示例:

getUser = async uid => {
  return this.db
    .collection('users')
    .doc(uid)
    .get()
    .then(async documentSnapshot => {
      const data = documentSnapshot.data()
      await hydrate(data, ['company', 'someOtherNestedRef-2', 'someOtherNestedRef-1'])
      return data
    })
}

限制: 此示例对于深度嵌套的引用无效


这真的很有帮助!谢谢 Chris。 - kingisaac95

6

我建议在每个评论中复制用户数据。在评论文档中创建一个名为user的对象字段,并提供显示评论所需的最少信息。因此,您的comment文档可能如下所示...

{
  id: 'CEXwQAHpk61vC0ulSdZy',
  text: 'This is a comment',
  user: {
    id: 'd5O4jTsLZHXmD1VJXwoE,
    name: 'Charlie Martin',
    avatar: 'https://...'
  }
}

现在,您已经拥有了显示评论所需的一切。如果有人点击评论作者,您可以使用该id加载评论者的完整个人资料。
在关系型数据库中,数据重复是不被赞同的,因为它们是通过外键处理这些情况的。
然而,在像firestore这样的NoSQL数据库中,实际上鼓励数据重复,以简化查询并减少发送到网络的数据量。
如果您为每个评论加载了完整的用户文档,那么您可能会加载比显示评论所需的更多关于用户的信息。

18
请注意,采用这种方法会获取过时的数据,例如如果用户更改其用户名(除非在每次用户更新时更新其所有出现次数)。 - Rafal Zawadzki
27
我认为这笔交易的规模更大 ;) 我点击了“Joe”,结果突然跳转到某个Bob的个人资料页面,我变得非常困惑:“是我的手指太胖了吗?是我的视力变模糊了吗?我很确定我想看的是Joe的资料,但显示的是我从未听说过的Bob...” 根据我的经验,普通用户对软件和数据库一无所知,他们只知道应用程序不能按照他们期望的那样工作。 - Rafal Zawadzki
15
现在我仔细想想,那其实挺奇怪的。如果是这样的话,我会编写一个云函数,在用户记录更新时更新所有重复的数据。 - Charlie Martin
2
@CharlieMartin 我认为这是正确的方法。我认为偶尔在其他地方更新用户记录比多次读取文档更好且成本更低。 - ihor.eth
@jprio 是的,这种权衡在NoSQL中经常出现。毕竟,它的主要目标是性能和可扩展性。你必须做出一些取舍。我可以说我已经做了很长时间了,我没有明显的错误,但是偶尔可能会遇到临时缓存失效问题,也就是过期状态。 - Charlie Martin
显示剩余3条评论

2

在ChrisRich的回答中补充一点。如果所需字段是引用数组,则可以使用以下代码:

import { get, set } from 'lodash'

const getReference = async documentReference => {
    const res = await documentReference.get()
    const data = res.data()

    if (data && documentReference.id) {
        data.uid = documentReference.id
    }

    return data
}

export default async (document, paths = []) => Promise.all(
    paths.map(async path => {
        const documentField = get(document, path)
        if (documentField.constructor.name === 'Array') {
            for (let i = 0; i < documentField.length; i++) {
                const documentReference = documentField[i];
                if (!documentReference || !documentReference.path) {
                    return console.warn(
                        `Error hydrating documentReference for path "${path}": Not found or invalid reference`
                    )
                }
                const result = await getReference(documentReference)
                documentField[i] = result;
            }
        }
        else {
            const documentReference = documentField;
            if (!documentReference || !documentReference.path) {
                return console.warn(
                    `Error hydrating documentReference for path "${path}": Not found or invalid reference`
                )
            }

            const result = await getReference(documentReference)
            set(document, path, result)
        }
    })
)

Chris的回答的这个扩展非常有帮助。谢谢Luis! - kingisaac95

0
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("Stock").whereEqualTo("user", userName).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<QuerySnapshot> task) {
        if (task.isComplete()) {
            Log.d(TAG, "onComplete stock : "+ task.getResult().getDocuments().toString());
            List<DocumentSnapshot> list = task.getResult().getDocuments();
            if (list != null && list.size()>0) {
                for (DocumentSnapshot doc: list) {
                    StockData stockData = new StockData();
                    stockData.setCatergory(doc.get("catergory").toString());
                    stockData.setDate(doc.get("date").toString());
                    stockData.setUser(doc.getString("date_time"));
                    stockData.setUser(doc.getString("user"));
                    stockData.setSale_price(doc.getDouble("sale_price"));
                }
            }
        }
    }
});

enter image description here


-2

我为此创建了一个解决方案,至少对我有效!

想象一下你有两个集合:用户和朋友,其中用户有一个文档:User1,朋友也有一个文档:Friend1。

因此,User1具有带有此文本的引用字段:Friends/Friend1。

您可以获取所有用户,并为每个用户构建如下的映射:

[
  {
    "User1":"Friend1"
  }
  {
    "Another User":"Another Friend"
  }
]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接