如何在 Cloud Firestore 查询中加入多个文档?
Posted
技术标签:
【中文标题】如何在 Cloud Firestore 查询中加入多个文档?【英文标题】:How to join multiple documents in a Cloud Firestore query? 【发布时间】:2018-06-05 11:17:49 【问题描述】:我有一个具有以下结构的 Cloud Firestore 数据库:
用户 [uid] 名称:“测试用户” 帖子 [编号] 内容:“只是一些测试帖。” 时间戳:(2017 年 12 月 22 日) uid:[uid]实际数据库中存在更多数据,以上只是说明了集合/文档/字段结构。
我的 Web 应用中有一个视图,我在其中显示帖子,并希望显示发帖用户的姓名。我正在使用以下查询来获取帖子:
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
我要做的是通过存储在帖子的uid字段中的uid查询用户对象,并将用户的名称字段添加到相应的loadedPosts对象中。如果我一次只加载一篇文章,这没问题,只需等待查询返回一个对象,然后在 .then()
函数中对用户文档进行另一次查询,依此类推。
但是,由于我一次获取多个帖子文档,因此在对每个帖子的 user/[uid]
文档调用 .get()
后,由于异步,我很难弄清楚如何将正确的用户映射到正确的帖子他们返回的方式。
谁能想到一个优雅的解决方案来解决这个问题?
【问题讨论】:
medium.com/google-developer-experts/… 链接到我在另一个帖子中的答案***.com/a/51671175/7212651 【参考方案1】:对我来说似乎相当简单:
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;
);
)
);
如果你想防止多次加载一个用户,你可以缓存用户数据客户端。在这种情况下,我建议将用户加载代码分解为辅助函数。但这将是上述内容的变体。
【讨论】:
哦,太好了,我刚刚测试过,效果很好!我在想,如果我这样处理它,doc.id 最终会成为每次调用用户 get().then() 的最后一次迭代文档的 id,我误解了在循环中定义回调! 当页面上有 n 个帖子时,这会导致 1+n 次数据库调用。有没有办法在一个数据库调用中下载收到的帖子的所有用户名? 不,在客户端 SDK 中没有这样的选项。见***.com/questions/46721517/… re:“您可以缓存用户数据客户端” - 我相信如果您附加一个侦听器而不是使用 .get(),Firebase SDK 将处理缓存数据,您可以问随心所欲地使用它,并始终获取缓存的数据。如果您订阅的数据发生更改,缓存本身将更新...【参考方案2】:我会进行 1 个用户文档调用和所需的帖子调用。
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;
);
);
【讨论】:
2 次调用而不是 4 次,很好! 这会加载 所有 用户,而不仅仅是撰写最后 3 个帖子的用户。虽然这在开发过程中可能会正常工作,但随着您添加更多用户,您将加载越来越多的不必要数据。 这不是一个好的解决方案。如果您的数据库中有一千个用户怎么办?您必须加载一千个用户文档才能获得 3 个所需的用户? 上面的代码可以用Cloud Function写吗?【参考方案3】:在尝试了多种解决方案后,我使用 RXJS combineLatest 完成了它,取操作员。使用 map 函数我们可以组合结果。
可能不是最佳解决方案,但它可以解决您的问题。
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');
,
【讨论】:
【参考方案4】:我的解决方案如下。
概念:您知道要获取信息的用户ID,在您的帖子列表中,您可以请求用户文档并将其保存为您的帖子项目中的承诺。承诺解决后,您将获得用户信息。
注意:我不测试下面的代码,但它是我的代码的简化版本。
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();
注意:afDb:AngularFirestore 在构造函数中初始化(通过 angularFire 库)
【讨论】:
【参考方案5】:如果您想加入 observables 而不是 Promise,请使用 combineLatest。以下是将 user 文档连接到 post 文档的示例:
getPosts(): Observable<Post[]>
let data: any;
return this.afs.collection<Post>('posts').valueChanges().pipe(
switchMap((r: any[]) =>
data = r;
const docs = r.map(
(d: any) => this.afs.doc<any>(`users/$d.user`).valueChanges()
);
return combineLatest(docs).pipe(
map((arr: any) => arr.reduce((acc: any, cur: any) => [acc].concat(cur)))
);
),
map((d: any) =>
let i = 0;
return d.map(
(doc: any) =>
const t = ...data[i], user: doc ;
++i;
return t;
);
)
);
此示例将每个文档连接到集合中,但如果您只想将一个文档连接到另一个文档,则可以简化此操作。
这假设您的 post 文档有一个 user 变量,其中包含文档的 userId。
J
【讨论】:
以上是关于如何在 Cloud Firestore 查询中加入多个文档?的主要内容,如果未能解决你的问题,请参考以下文章
从 Cloud Firestore 在 MatDatepicker 中加载时间戳 |火力基地
如何在 Cloud Firestore 中使用文档 ID 执行集合组查询
查询时如何在 Flutter (Dart) 中使用变量 Cloud Firestore
如何使用 Cloud Firestore 查询用户待处理的好友请求?