Cloud Firestore: как получить ссылку на документ внутри моего запроса коллекции и сопоставить ее как значение JSON?

Допустим, у меня есть коллекция комментариев. Каждый объект комментария имеет "ссылку на документ" для пользователя, который разместил. Мне нужен запрос, который вернет список комментариев, включая значение каждой пользовательской ссылки, поэтому мой запрос возвращает хорошо отформатированные объекты комментариев Json.


person Mauricio Silva    schedule 22.10.2017    source источник


Ответы (6)


Аналогичный вопрос был задан здесь Для чего нужен ссылочный тип данных firestore?, я не думаю, что можно делать то, что вы просите, в соответствии с этим ответом https://stackoverflow.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)
    })
  })
})

Для многих комментариев это добавит много накладных расходов. Возможно, вы можете написать CloudFunction, которая сделает всю работу за вас на стороне сервера и вернет вам отформатированный JSON.

Похоже, они могут работать над поддержкой этого в будущем: https://stackoverflow.com/a/46614683/473453

person user473453    schedule 23.10.2017

Предлагаю дублировать данные пользователя в каждом комментарии. Создайте поле в документе комментария, которое представляет собой объект с именем user, и укажите минимальный объем информации, необходимый для отображения комментария. Итак, ваш comment документ может выглядеть ...

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

Теперь у вас есть все необходимое для отображения комментария. Если кто-то нажимает на автора комментария, вы можете взять идентификатор и использовать его для загрузки полного профиля комментатора.

Дублирование данных не одобряется в реляционных базах данных, поскольку они созданы для обработки этих сценариев с внешними ключами.

Однако в базах данных NoSQL, таких как firestore, дублирование данных фактически поощряется для упрощения запросов, а также уменьшения объема данных, отправляемых по сети.

Если бы вы загрузили полный user документ для каждого комментария, вы, вероятно, загрузили бы гораздо больше информации о пользователе, чем требуется для отображения комментария.

person Charlie Martin    schedule 05.04.2018
comment
Обратите внимание, что при таком подходе вы получите устаревшие данные, например, если пользователь изменит свое имя пользователя (если при каждом обновлении пользователя вы не обновите все его вхождения) - person Rafal Zawadzki; 15.04.2018
comment
Верный. Однако Google по-прежнему рекомендует этот подход. На самом деле это не имеет значения, потому что идентификатор все еще действителен. Например, я могу увидеть в ветке комментариев сообщение пользователя Джо, но когда я нажимаю на этого пользователя, я вижу, что он изменил свое имя на Боб. Ничего страшного. - person Charlie Martin; 28.04.2018
comment
Что ж, размер сделки, на мой взгляд, больше;) Я нажал на Джо и внезапно попал на профиль какого-то Боба - и я стал действительно сбитым с толку пользователем: мои пальцы такие толстые? Мое зрение нечеткое? Я почти уверен, что хотел увидеть профиль Джо, и это какой-то Боб, о котором я никогда не слышал ... Исходя из моего опыта, среднестатистические пользователи понятия не имеют о программном обеспечении и базах данных - они просто знают, что приложение не работает так, как они ожидайте, что это сработает - person Rafal Zawadzki; 15.05.2018
comment
Сейчас, когда я думаю об этом, это было бы довольно странно. В этом случае я бы написал облачную функцию, которая обновляет все повторяющиеся данные при обновлении пользовательской записи. - person Charlie Martin; 15.05.2018
comment
@CharlieMartin Я думаю, что это правильный подход. Я думаю, что лучше и дешевле периодически обновлять пользовательские записи в других местах, чем читать документ несколько раз. - person ihorbond; 12.05.2020

Меня это тоже чертовски раздражало. Я сделал вспомогательную утилиту, которая может автоматически заполнять первый уровень.

Помощник

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

Ограничение: этот пример не будет работать для глубоких вложенных ссылок.

person ChrisRich    schedule 22.09.2019
comment
Это было действительно полезно! Спасибо, Крис. - person kingisaac95; 29.04.2021

Добавление в 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)
        }
    })
)
person Luis Sousa    schedule 23.03.2020
comment
Это расширение ответа Криса было очень полезным. Спасибо, Луис! - person kingisaac95; 29.04.2021

Я создаю для этого решение, по крайней мере, работаю для меня!

Представьте, что у вас есть 2 коллекции: пользователи и друзья, а у пользователей один документ: у пользователя User1 и Friends тоже есть один документ: Friend1.

Итак, у User1 есть справочное поле с таким текстом: Friends / Friend1.

Вы можете получить всех пользователей, и для каждого из них вы можете построить карту следующим образом:

[
  {
    "User1":"Friend1"
  }
  {
    "Another User":"Another Friend"
  }
]
person Tiago Pereira    schedule 10.08.2019

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

введите здесь описание изображения

person Lahiru Liyanage    schedule 02.04.2020