Допустим, у меня есть коллекция комментариев. Каждый объект комментария имеет "ссылку на документ" для пользователя, который разместил. Мне нужен запрос, который вернет список комментариев, включая значение каждой пользовательской ссылки, поэтому мой запрос возвращает хорошо отформатированные объекты комментариев Json.
Cloud Firestore: как получить ссылку на документ внутри моего запроса коллекции и сопоставить ее как значение JSON?
Ответы (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 а>
Предлагаю дублировать данные пользователя в каждом комментарии. Создайте поле в документе комментария, которое представляет собой объект с именем user
, и укажите минимальный объем информации, необходимый для отображения комментария. Итак, ваш comment
документ может выглядеть ...
{
id: 'CEXwQAHpk61vC0ulSdZy',
text: 'This is a comment',
user: {
id: 'd5O4jTsLZHXmD1VJXwoE,
name: 'Charlie Martin',
avatar: 'https://...'
}
}
Теперь у вас есть все необходимое для отображения комментария. Если кто-то нажимает на автора комментария, вы можете взять идентификатор и использовать его для загрузки полного профиля комментатора.
Дублирование данных не одобряется в реляционных базах данных, поскольку они созданы для обработки этих сценариев с внешними ключами.
Однако в базах данных NoSQL, таких как firestore, дублирование данных фактически поощряется для упрощения запросов, а также уменьшения объема данных, отправляемых по сети.
Если бы вы загрузили полный user
документ для каждого комментария, вы, вероятно, загрузили бы гораздо больше информации о пользователе, чем требуется для отображения комментария.
Меня это тоже чертовски раздражало. Я сделал вспомогательную утилиту, которая может автоматически заполнять первый уровень.
Помощник
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
})
}
Ограничение: этот пример не будет работать для глубоких вложенных ссылок.
Добавление в 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)
}
})
)
Я создаю для этого решение, по крайней мере, работаю для меня!
Представьте, что у вас есть 2 коллекции: пользователи и друзья, а у пользователей один документ: у пользователя User1 и Friends тоже есть один документ: Friend1.
Итак, у User1 есть справочное поле с таким текстом: Friends / Friend1.
Вы можете получить всех пользователей, и для каждого из них вы можете построить карту следующим образом:
[
{
"User1":"Friend1"
}
{
"Another User":"Another Friend"
}
]
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"));
}
}
}
}
});