web-dev-qa-db-ja.com

Cloud Firestoreクエリで複数のドキュメントを結合する方法は?

次の構造のCloud Firestore DBがあります。

  • ユーザー
    • [uid]
      • 名前:「テストユーザー」
  • 投稿
    • [id]
      • 内容:「テストポストだけ。」
      • タイムスタンプ:(2017年12月22日)
      • uid:[uid]

実際のDBにはより多くのデータが存在します。上記はコレクション/ドキュメント/フィールド構造を示しています。

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オブジェクトに追加することです。一度に1つの投稿のみをロードする場合、これは問題になりません。クエリがオブジェクトとともに戻ってくるのを待って、.then()関数でユーザードキュメントに別のクエリを作成するなどです。

ただし、一度に複数の投稿ドキュメントを取得するため、各投稿の_user/[uid]_ドキュメントで.get()を呼び出した後、正しいユーザーを正しい投稿にマッピングする方法を見つけるのに苦労しています非同期的な方法に戻ります。

誰もがこの問題のエレガントな解決策を考えることができますか?

13
saricden

1人のユーザーdoc呼び出しと必要な投稿呼び出しを行います。

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

私にはかなり簡単に思えます:

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

ユーザーが複数回ロードされるのを防ぎたい場合は、ユーザーデータのクライアント側をキャッシュできます。その場合、ユーザーロードコードをヘルパー関数に組み込むことをお勧めします。ただし、上記のバリエーションになります。

17

以下の私の解決策。

コンセプト:情報を取得したいユーザー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 libによる)

1