web-dev-qa-db-fra.com

Cloud Firestore deep get avec sous-collection

Disons que nous avons une collection racine nommée 'todos'.

Chaque document de cette collection a:

  1. title: String
  2. sous-collection nommée todo_items

Chaque document de la sous-collection todo_items a

  1. title: String
  2. completed: Boolean

Je sais que l'interrogation dans Cloud Firestore est peu profonde par défaut, ce qui est excellent, mais existe-t-il un moyen d'interroger le todos et d'obtenir des résultats qui incluent la sous-collection todo_items automatiquement?

En d'autres termes, comment puis-je inclure dans la requête suivante le todo_items sous-collection?

db.collection('todos').onSnapshot((snapshot) => {
  snapshot.docChanges.forEach((change) => {
    // ...
  });
});
53
qeroqazo

Ce type de requête n'est pas pris en charge, bien que nous puissions en tenir compte ultérieurement.

37
Dan McGrath

Si vous souhaitez toujours savoir comment effectuer une requête détaillée dans Firestore, voici une version de la fonction cloud getAllTodos que j'ai proposée, qui renvoie tous les "todos" contenant la sous-collection "todo_items".

exports.getAllTodos = function (req, res) {
    getTodos().
        then((todos) => {
            console.log("All Todos " + todos) // All Todos with its todo_items sub collection.
            return res.json(todos);
        })
        .catch((err) => {
            console.log('Error getting documents', err);
            return res.status(500).json({ message: "Error getting the all Todos" + err });
        });
}

function getTodos(){
    var todosRef = db.collection('todos');

    return todosRef.get()
        .then((snapshot) => {
            let todos = [];
            return Promise.all(
                snapshot.docs.map(doc => {  
                        let todo = {};                
                        todo.id = doc.id;
                        todo.todo = doc.data(); // will have 'todo.title'
                        var todoItemsPromise = getTodoItemsById(todo.id);
                        return todoItemsPromise.then((todoItems) => {                    
                                todo.todo_items = todoItems;
                                todos.Push(todo);         
                                return todos;                  
                            }) 
                })
            )
            .then(todos => {
                return todos.length > 0 ? todos[todos.length - 1] : [];
            })

        })
}


function getTodoItemsById(id){
    var todoItemsRef = db.collection('todos').doc(id).collection('todo_items');
    let todo_items = [];
    return todoItemsRef.get()
        .then(snapshot => {
            snapshot.forEach(item => {
                let todo_item = {};
                todo_item.id = item.id;
                todo_item.todo_item = item.data(); // will have 'todo_item.title' and 'todo_item.completed'             
                todo_items.Push(todo_item);
            })
            return todo_items;
        })
}
3
Malick

Comme indiqué dans d'autres réponses, vous ne pouvez pas demander des requêtes approfondies.

Ma recommandation: dupliquez vos données le moins possible.

Je rencontre ce même problème avec "la possession d'un animal de compagnie". Dans mes résultats de recherche, je dois afficher chaque animal de compagnie appartenant à un utilisateur, mais je dois également pouvoir rechercher des animaux de compagnie par eux-mêmes. J'ai fini par dupliquer les données. Je vais avoir une propriété de tableau pets sur chaque utilisateur, ainsi qu'une sous-collection d'animaux. Je pense que c'est le mieux que nous puissions faire avec ce genre de scénario.

0
lustig

J'ai utilisé AngularFirestore (afs) et TypeScript:

import { map, flatMap } from 'rxjs/operators';
import { combineLatest } from 'rxjs';

interface DocWithId {
  id: string;
}

convertSnapshots<T>(snaps) {
  return <T[]>snaps.map(snap => {
    return {
      id: snap.payload.doc.id,
      ...snap.payload.doc.data()
    };
  });
}

getDocumentsWithSubcollection<T extends DocWithId>(
    collection: string,
    subCollection: string
  ) {
    return this.afs
      .collection(collection)
      .snapshotChanges()
      .pipe(
        map(this.convertSnapshots),
        map((documents: T[]) =>
          documents.map(document => {
            return this.afs
             .collection(`${collection}/${document.id}/${subCollection}`)
              .snapshotChanges()
              .pipe(
                map(this.convertSnapshots),
                map(subdocuments =>
                  Object.assign(document, { [subCollection]: subdocuments })
                )
              );
          })
        ),
        flatMap(combined => combineLatest(combined))
      );
  }
  
0
user2734839

Je suis confronté au même problème, mais avec IOS, de toute façon si je reçois votre question et si vous utilisez un identifiant automatique pour un document de collection de tâches à effectuer, il sera facile si vous stockez l'ID de document aussi loin avec le champ de titre dans mon cas:

let ref = self.db.collection("collectionName").document()

let data  = ["docID": ref.documentID,"title" :"some title"]

Ainsi, lorsque vous récupérez, disons un tableau de tâches à effectuer et lorsque vous cliquez sur un élément, vous pouvez naviguer aussi facilement par le chemin

ref = db.collection("docID/\(todo_items)")

J'aimerais pouvoir vous donner le code exact, mais je ne connais pas Javascript

0
Ali Adil