2017-10-15 4 views
6

J'essaie actuellement Firestore, et je suis coincé à quelque chose de très simple: "mise à jour d'un tableau (alias un sous-document)".Comment mettre à jour un "tableau d'objets" avec Firestore?

Ma structure DB est super simple. Par exemple:

proprietary: "John Doe" 
sharedWith: 
    [ 
    {who: "[email protected]", when:timestamp} 
    {who: "[email protected]", when:timestamp} 
    ] 

Je suis en train (sans succès) pour pousser les nouveaux enregistrements dans shareWith tableau d'objets.

J'ai essayé:

// With SET 
firebase.firestore() 
.collection('proprietary') 
.doc(docID) 
.set(
    { sharedWith: [{ who: "[email protected]", when: new Date() }] }, 
    { merge: true } 
) 

// With UPDATE 
firebase.firestore() 
.collection('proprietary') 
.doc(docID) 
.update({ sharedWith: [{ who: "[email protected]", when: new Date() }] }) 

Aucun fonctionne. Ces requêtes écrasent mon tableau.

La réponse pourrait être simple, mais je ne pouvions pas trouver ...

Merci

Répondre

8

Il n'y a actuellement aucun moyen de mettre à jour un seul élément de tableau (ou ajouter/supprimer un seul élément) en Firestore de nuage.

Ce code ici:

firebase.firestore() 
.collection('proprietary') 
.doc(docID) 
.set(
    { sharedWith: [{ who: "[email protected]", when: new Date() }] }, 
    { merge: true } 
) 

Cela dit pour définir le document à proprietary/docID tel que sharedWith = [{ who: "[email protected]", when: new Date() } mais pour ne pas affecter les propriétés du document existant. C'est très similaire à l'appel update() que vous avez fourni mais l'appel set() avec créer le document s'il n'existe pas alors que l'appel update() échouera.

Vous avez donc deux options pour réaliser ce que vous voulez.

Option 1 - Mettre tout le tableau

Call set() avec le contenu du tableau, ce qui nécessitera la lecture des données actuelles de la DB première. Si vous êtes préoccupé par les mises à jour simultanées, vous pouvez faire tout cela dans une transaction.

Option 2 - Utilisez une sous-collection

Vous pourriez faire sharedWith une sous-collection du document principal. Ensuite ajouter un seul élément ressemblerait à ceci:

firebase.firestore() 
    .collection('proprietary') 
    .doc(docID) 
    .collection('sharedWith') 
    .add({ who: "[email protected]", when: new Date() }) 

Bien sûr, cela vient avec de nouvelles limites. Vous ne seriez pas en mesure d'interroger les documents en fonction de la personne avec laquelle ils sont partagés, et vous ne pourrez pas obtenir le doc et toutes les données sharedWith en une seule opération.

+0

C'est tellement frustrant ... mais merci de m'avoir fait savoir que je ne deviens pas fou. – ItJustWerks

+8

c'est un gros inconvénient, Google doit le réparer dès que possible. –

1

Autres que les réponses mentionnées ci-dessus. Cela va le faire. Utilisation de Angular 5 et AngularFire2. ou utilisez firebase.firestore() à la place.afs

// say you have have the following object and 
    // database structure as you mentioned in your post 
    data = { who: "[email protected]", when: new Date() }; 

    ...othercode 


    addSharedWith(data) { 

    const postDocRef = this.afs.collection('posts').doc('docID'); 

    postDocRef.subscribe(post => { 

     // Grab the existing sharedWith Array 
     // If post.sharedWith doesn`t exsit initiated with empty array 
     const foo = { 'sharedWith' : post.sharedWith || []}; 

     // Grab the existing sharedWith Array 
     foo['sharedWith'].push(data); 

     // pass updated to fireStore 
     postsDocRef.update(foo); 
     // using .set() will overwrite everything 
     // .update will only update existing values, 
     // so we initiated sharedWith with empty array 
    }); 
} 
2

Vous pouvez utiliser une transaction (https://firebase.google.com/docs/firestore/manage-data/transactions) pour obtenir le tableau, appuyez sur et puis mettre à jour le document:

const booking = { some: "data" }; 
    const userRef = this.db.collection("users").doc(userId); 

    this.db.runTransaction(transaction => { 
     // This code may get re-run multiple times if there are conflicts. 
     return transaction.get(userRef).then(doc => { 
      if (!doc.data().bookings) { 
       transaction.set({ 
        bookings: [booking] 
       }); 
      } else { 
       const bookings = doc.data().bookings; 
       bookings.push(booking); 
       transaction.update(userRef, { bookings: bookings }); 
      } 
     }); 
    }).then(function() { 
     console.log("Transaction successfully committed!"); 
    }).catch(function (error) { 
     console.log("Transaction failed: ", error); 
    }); 
0

Pour construire sur Sam Stern's answer, il y a aussi une option 3 ce qui a rendu les choses plus faciles pour moi et qui utilise ce que Google appelle une carte, qui est essentiellement un dictionnaire.

Je pense qu'un dictionnaire est bien meilleur pour le cas d'utilisation que vous décrivez. J'utilise généralement des tableaux pour des choses qui ne sont pas vraiment mises à jour, donc elles sont plus ou moins statiques. Mais pour les choses qui s'écrivent beaucoup, en particulier les valeurs qui doivent être mises à jour pour les champs qui sont liés à quelque chose d'autre dans la base de données, les dictionnaires s'avèrent beaucoup plus faciles à maintenir et à utiliser.

Donc, pour votre cas particulier, la structure DB ressemblerait à ceci:

proprietary: "John Doe" 
sharedWith:{ 
    whoEmail1: {when: timestamp}, 
    whoEmail2: {when: timestamp} 
} 

Cela vous permettra d'effectuer les opérations suivantes:

var whoEmail = '[email protected]'; 

var sharedObject = {}; 
sharedObject['sharedWith.' + whoEmail + '.when'] = new Date(); 
sharedObject['merge'] = true; 

firebase.firestore() 
.collection('proprietary') 
.doc(docID) 
.update(sharedObject); 

La raison de définir l'objet comme une variable est que l'utilisation de 'sharedWith.' + whoEmail + '.when' directement dans la méthode set entraînera une erreur, au moins lors de son utilisation dans une fonction cloud Node.js.