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

Posted

技术标签:

【中文标题】Cloud Firestore:如何在我的集合查询中获取文档引用并将其映射为 JSON 值?【英文标题】:Cloud Firestore: how to fetch a document reference inside my collection query and map it as a JSON value? 【发布时间】:2018-04-03 09:14:24 【问题描述】:

假设我有一个 cmets 集合。每个评论对象对发布的用户都有一个“doc ref”。我需要一个查询,它会返回一个包含每个用户引用的值的 cmets 列表,所以我的查询返回一个格式很好的 Json 注释对象。

【问题讨论】:

【参考方案1】:

What is firestore Reference data type good for? 在这里提出了类似的问题,我认为根据这个答案https://***.com/a/46570119/473453 做你所问的事情是不可能的。

您必须自己加载每个引用,例如

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)
    )
  )
)

对于许多 cmets,这会增加很多开销。也许您可以编写一个 CloudFunction,在服务器端为您完成所有工作并返回一个格式化的 JSON。

看起来他们可能会在未来努力支持这一点:https://***.com/a/46614683/473453

【讨论】:

【参考方案2】:

我建议您在每条评论中复制用户数据。在评论文档中创建一个名为 user 的对象的字段,并提供显示评论所需的最少信息量。所以你的comment 文档可能看起来像...


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

现在,您拥有显示评论所需的一切。如果有人点击评论的作者,您可以获取该 id 并使用它来加载评论者的完整个人资料。

在关系数据库中数据重复是不受欢迎的,因为它们是为使用外键处理这些场景而构建的。

然而,在 Firestore 等 NoSQL 数据库中,实际上鼓励数据复制以简化查询并减少通过网络发送的数据量。

如果您为每条评论加载了完整的 user 文档,那么您加载的用户信息可能比显示评论所需的信息多得多。

【讨论】:

请注意,使用这种方法,您将获得过时的数据,例如,如果用户更改其用户名(除非每次用户更新您都会更新所有出现的数据) 正确。不过,这仍然是 Google 推荐的方法。这并不重要,因为 ID 仍然有效。例如,我可能会在评论线程中看到用户“Joe”的帖子,但是当我单击该用户时,我看到他们已将姓名更改为“Bob”。没什么大不了的。 好吧,在我看来,这笔交易的规模更大;)我点击了“Joe”,突然我进入了某个 Bob 的个人资料——我变成了一个非常困惑的用户:“是我的手指这么胖?我的视力是不是很模糊?我很确定我想看看乔的个人资料,这是我从未听说过的鲍勃……”根据我的经验,普通用户对软件和数据库一无所知 - 他们只知道应用程序没有按照他们期望的那样工作 现在想想,这实际上会很奇怪。在这种情况下,我会编写一个云函数,在更新用户记录时更新所有重复数据 @CharlieMartin 我认为这是正确的方法。我认为偶尔更新其他地方的用户记录而不是多次阅读文档更好,成本更低【参考方案3】:

这也让我非常恼火。我做了一个实用助手,可以自动填充第一级。

助手

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
    )

限制:此示例不适用于深层嵌套引用

【讨论】:

这真的很有帮助!谢谢克里斯。【参考方案4】:

添加到 ChrisRich response。如果所需字段是引用数组,则可以使用以下代码:

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)
        
    )
)

【讨论】:

克里斯回答的这个扩展非常有帮助。谢谢路易斯!【参考方案5】:
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"));
                
            
        
    
);

【讨论】:

【参考方案6】:

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

假设你有 2 个集合:用户和朋友,用户有一个文档:用户 1,朋友也有一个文档:朋友 1。

所以 User1 有一个包含此文本的参考字段:Friends/Friend1。

您可以获取所有用户,并且您可以为每个用户构建这样的地图:

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

【讨论】:

以上是关于Cloud Firestore:如何在我的集合查询中获取文档引用并将其映射为 JSON 值?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Javascript 在 Cloud Firestore 中创建集合?

使用规则禁用 Firebase Cloud Firestore 中的查询集合

Cloud Firestore 查询文档及其集合

如何在Cloud Firestore中仅填充一次日期字段?

如何在 Cloud Firestore 查询中加入多个文档?

Cloud Firestore 集合查询不起作用