-->

Cloud Firestore: how to fetch a document reference

2020-06-10 07:29发布

问题:

Let's say I have a collection of comments. Every comment object has a "doc ref" to the user who posted. I need a query that will return a list of comments including the value of every single user reference, so my query returns a nice formatted of Json comment objects.

回答1:

A similar question was asked here What is firestore Reference data type good for?, I don't think it is possible to do what you are asking according to this answer https://stackoverflow.com/a/46570119/473453.

You have to load every reference yourself, e.g.

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

For many comments this will add a lot of overhead. Maybe you can write a CloudFunction that does the work for you all on the server side and returns you a formatted JSON.

It looks like they might e working on supporting this in the future though: https://stackoverflow.com/a/46614683/473453



回答2:

I suggest you duplicate the user data in each comment. Create a field in the comment doc that is an object named user and provide the minimum amount of information required to display the comment. So your comment doc might look like...

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

Now, you have everything you need to display the comment. If someone clicks the author of the comment, you can then take the id and use it to load the full profile of the commentor.

Data duplication is frowned upon in relational databases since they are built to handle these scenarios with foreign keys.

However, in NoSQL databases like firestore, data duplication is actually encouraged to simplify queries as well as reduce the amount of data sent over the network.

If you had loaded the full user document for each comment, you'd likely be loading much more information about the user than is required to display the comment.



回答3:

This annoyed the hell out of me as well. I made a utility helper, that can automatically populate the first level.

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 }

Example usage:

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

Limitation: This example will not work for deep nested references



回答4:

I create a solution for this, at least work for me!

Imagine u have 2 collections: users and friends and Users has one document: User1 and Friends has one document too: Friend1.

So User1 has a reference field with this text: Friends/Friend1.

U can get all Users and for each one u can build a map like this:

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


回答5:

Adding to ChrisRich response. If the desired field is an array of references you can use the following code:

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


回答6:

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"));
                }
            }
        }
    }
});