Javascript 如何在云Firestore查询中连接多个文档?
我有一个云Firestore DB,其结构如下:Javascript 如何在云Firestore查询中连接多个文档?,javascript,firebase,google-cloud-firestore,Javascript,Firebase,Google Cloud Firestore,我有一个云Firestore DB,其结构如下: 使用者 [uid] 名称:“测试用户” 职位 [id] 内容:“只是一些测试帖子。” 时间戳:(2017年12月22日) uid:[uid] 实际数据库中存在更多的数据,以上只是说明了集合/文档/字段结构 我在我的web应用程序中有一个视图,其中我正在显示帖子,并希望显示发布帖子的用户的姓名。我正在使用以下查询获取帖子: let loadedPosts = {}; posts = db.collection('posts')
- 使用者
- [uid]
- 名称:“测试用户”
- [uid]
- 职位
- [id]
- 内容:“只是一些测试帖子。”
- 时间戳:(2017年12月22日)
- uid:[uid]
- [id]
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
const postDocs = docSnaps.docs;
for (let i in postDocs) {
loadedPosts[postDocs[i].id] = postDocs[i].data();
}
});
// Render loadedPosts later
我要做的是通过post的uid字段中存储的uid查询user对象,并将user's name字段添加到相应的loadedPosts对象中。如果我一次只加载一篇文章,这不会有问题,只要等待查询返回一个对象,然后在函数中()
对用户文档进行另一次查询,依此类推
然而,因为我一次得到多个post文档,我很难在调用每个post的user/[uid]
文档上的.get()
后找出如何将正确的用户映射到正确的post,因为它们返回的方式是异步的
有人能想出一个解决这个问题的好办法吗?我觉得这很简单:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
db.collection('users').child(doc.data().uid).get().then((userDoc) => {
loadedPosts[doc.id].userName = userDoc.data().name;
});
})
});
如果要防止多次加载用户,可以缓存用户数据客户端。在这种情况下,我建议将用户加载代码分解为一个helper函数。但它将是上面的一个变体。我将执行一个用户doc调用和所需的posts调用
let users = {} ;
let loadedPosts = {};
db.collection('users').get().then((results) => {
results.forEach((doc) => {
users[doc.id] = doc.data();
});
posts = db.collection('posts').orderBy('timestamp', 'desc').limit(3);
posts.get().then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
loadedPosts[doc.id].userName = users[doc.data().uid].name;
});
});
我的解决方案如下
概念:你知道你想要获得信息的用户id,在你的帖子列表中,你可以请求用户文档并将其保存为你的帖子项目中的承诺。在promise resolve之后,您将获得用户信息
注意:我不测试下面的代码,但它是我代码的简化版本
let posts: Observable<{}[]>; // you can display in HTML directly with | async tag
this.posts = this.listenPosts()
.map( posts => {
posts.forEach( post => {
post.promise = this.getUserDoc( post.uid )
.then( (doc: DocumentSnapshot) => {
post.userName = doc.data().name;
});
}); // end forEach
return posts;
});
// normally, i keep in provider
listenPosts(): Observable<any> {
let fsPath = 'posts';
return this.afDb.collection( fsPath ).valueChanges();
}
// to get the document according the user uid
getUserDoc( uid: string ): Promise<any> {
let fsPath = 'users/' + uid;
return this.afDb.doc( fsPath ).ref.get();
}
let posts:可观察;//您可以使用| async标记直接在HTML中显示
this.posts=this.listenPosts()
.map(posts=>{
posts.forEach(post=>{
post.promise=this.getUserDoc(post.uid)
.然后((文档:DocumentSnapshot)=>{
post.userName=doc.data().name;
});
});//结束forEach
返回岗位;
});
//通常情况下,我会留在家里
listenPosts():可观察{
设fsPath='posts';
返回此.afDb.collection(fsPath).valueChanges();
}
//根据用户uid获取文档
getUserDoc(uid:string):承诺{
让fsPath='users/'+uid;
返回这个.afDb.doc(fsPath).ref.get();
}
注意:afDb:AngularFirestore它是在构造函数中初始化的(由angularFire lib)尝试多个解决方案后,我用RXJS CombineTest完成它,使用运算符。使用映射功能,我们可以组合结果。 可能不是一个最佳的解决方案,但在这里它解决了你的问题
combineLatest(
this.firestore.collection('Collection1').snapshotChanges(),
this.firestore.collection('Collection2').snapshotChanges(),
//In collection 2 we have document with reference id of collection 1
)
.pipe(
take(1),
).subscribe(
([dataFromCollection1, dataFromCollection2]) => {
this.dataofCollection1 = dataFromCollection1.map((data) => {
return {
id: data.payload.doc.id,
...data.payload.doc.data() as {},
}
as IdataFromCollection1;
});
this.dataofCollection2 = dataFromCollection2.map((data2) => {
return {
id: data2.payload.doc.id,
...data2.payload.doc.data() as {},
}
as IdataFromCollection2;
});
console.log(this.dataofCollection2, 'all feeess');
const mergeDataFromCollection =
this.dataofCollection1.map(itm => ({
payment: [this.dataofCollection2.find((item) => (item.RefId === itm.id))],
...itm
}))
console.log(mergeDataFromCollection, 'all data');
},
哦,太好了,我刚刚测试过,它工作得非常好!我在想,如果我这样做的话,doc.id最终将成为对用户get()的每次调用的上一次迭代的doc的id。然后(),这就误解了我在循环中定义回调!当页面上有n个帖子时,会导致1+n个数据库调用。有没有一种方法可以在一次数据库调用中下载所接收帖子的所有用户名?没有,在客户端SDK中没有这样的选项。请参阅re:“您可以缓存用户数据客户端”-我相信,如果您附加一个侦听器而不是使用.get(),Firebase SDK将处理缓存数据,您可以随时请求,并始终获取缓存数据。如果您订阅的数据发生更改,缓存本身将更新…2次调用而不是4次,很好!这将加载所有用户,而不仅仅是最后3篇文章的作者。虽然这在开发过程中可以很好地工作,但随着用户的增加,将加载越来越多不必要的数据。这不是一个好的解决方案。如果数据库中有1000个用户,该怎么办?你必须加载一千个用户文档才能获得所需的3个用户?链接到另一篇文章中的我的答案