Come aggiornare un "array di oggetti" con Firestore?


104

Attualmente sto provando Firestore, e sono bloccato su qualcosa di molto semplice: "aggiornare un array (aka un documento secondario)".

La mia struttura DB è semplicissima. Per esempio:

proprietary: "John Doe",
sharedWith:
  [
    {who: "first@test.com", when:timestamp},
    {who: "another@test.com", when:timestamp},
  ],

Sto cercando (senza successo) di inserire nuovi record in una shareWithserie di oggetti.

Ho provato:

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

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

Nessuno funziona. Queste query sovrascrivono il mio array.

La risposta potrebbe essere semplice, ma non sono riuscita a trovarla ...

Risposte:


71

Modifica 13/08/2018 : ora è disponibile il supporto per le operazioni di array nativi in ​​Cloud Firestore. Vedi la risposta di Doug di seguito.


Al momento non è possibile aggiornare un singolo elemento dell'array (o aggiungere / rimuovere un singolo elemento) in Cloud Firestore.

Questo codice qui:

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

Questo dice di impostare il documento in proprietary/docIDmodo tale sharedWith = [{ who: "third@test.com", when: new Date() }ma di non influenzare le proprietà del documento esistente. È molto simile alla update()chiamata che hai fornito, tuttavia la set()chiamata con crea il documento se non esiste mentre la update()chiamata fallirà.

Quindi hai due opzioni per ottenere ciò che desideri.

Opzione 1: imposta l'intero array

Chiama set()con l'intero contenuto dell'array, che richiederà prima la lettura dei dati correnti dal DB. Se sei preoccupato per gli aggiornamenti simultanei, puoi fare tutto questo in una transazione.

Opzione 2: utilizzare una sottoraccolta

Potresti creare sharedWithuna sottoraccolta del documento principale. Quindi l'aggiunta di un singolo elemento sarebbe simile a questo:

firebase.firestore()
  .collection('proprietary')
  .doc(docID)
  .collection('sharedWith')
  .add({ who: "third@test.com", when: new Date() })

Ovviamente questo comporta nuove limitazioni. Non saresti in grado di interrogare i documenti in base alle persone con cui sono condivisi, né potresti ottenere il documento e tutti i sharedWithdati in un'unica operazione.


9
È così frustrante ... ma grazie per avermi fatto sapere che non sto impazzendo.
ItJustWerks

51
questo è un grosso svantaggio, Google deve risolverlo al più presto.
Sajith Mantharath

3
La risposta di @ DougGalante indica che il problema è stato risolto. Usa il arrayUnionmetodo.
quicklikerabbit

152

Firestore ora ha due funzioni che consentono di aggiornare un array senza riscrivere l'intera cosa.

Link: https://firebase.google.com/docs/firestore/manage-data/add-data , in particolare https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array

Aggiorna gli elementi in un array

Se il documento contiene un campo array, è possibile utilizzare arrayUnion () e arrayRemove () per aggiungere e rimuovere elementi. arrayUnion () aggiunge elementi a un array ma solo elementi non già presenti. arrayRemove () rimuove tutte le istanze di ogni dato elemento.


56
C'è un modo per aggiornare un indice specifico dall'array?
Artur Carvalho

1
Come utilizzare questa funzione di aggiornamento dell'array con "react-native-firebase"? (Non riesco a trovarlo sui documenti ufficiali di react-native-firebase)
kernelman

4
@ArturCarvalho No, il motivo è spiegato in questo video youtube.com/…
Adam

@ArturCarvalho hai trovato qualche soluzione per aggiornare un indice specifico dall'array?
Yogendra Patel

3
per chi ha bisogno di farlo sul client, usa "import * as firebase from 'firebase / app';" quindi "firebase.firestore.FieldValue.arrayUnion (NEW_ELEMENT)"
michelepatrassi

15

Puoi utilizzare una transazione ( https://firebase.google.com/docs/firestore/manage-data/transactions ) per ottenere l'array, inviarlo e quindi aggiornare il documento:

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

1
All'istruzione if dovresti cambiarla in questa, perché ti manca l' documentReferenceaggiunta userRefcome mostrato: transaction.set(userRef, { bookings: [booking] });
Ilir Hushi

8

Ecco l'ultimo esempio dalla documentazione di Firestore:

firebase.firestore.FieldValue. ArrayUnion

var washingtonRef = db.collection("cities").doc("DC");

// Atomically add a new region to the "regions" array field.
washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")
});

// Atomically remove a region from the "regions" array field.
washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayRemove("east_coast")
});

@nifCody, questo aggiungerà effettivamente un nuovo elemento stringa "greater_virginia" a un array esistente "regioni". L'ho testato con successo e sicuramente non ho aggiunto "oggetto". È in sincronia con la domanda come affermato: "spingere nuovi record".
Veeresh Devireddy


3

Per costruire sulla risposta di Sam Stern , c'è anche una terza opzione che mi ha reso le cose più facili e che consiste nell'usare ciò che Google chiama una mappa, che è essenzialmente un dizionario.

Penso che un dizionario sia di gran lunga migliore per il caso d'uso che stai descrivendo. Di solito uso gli array per cose che non sono aggiornate troppo, quindi sono più o meno statiche. Ma per le cose che vengono scritte molto, in particolare i valori che devono essere aggiornati per i campi che sono collegati a qualcos'altro nel database, i dizionari si dimostrano molto più facili da mantenere e lavorare.

Quindi, per il tuo caso specifico, la struttura del DB sarebbe simile a questa:

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

Questo ti permetterà di fare quanto segue:

var whoEmail = 'first@test.com';

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

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

Il motivo per definire l'oggetto come una variabile è che l'utilizzo 'sharedWith.' + whoEmail + '.when'direttamente nel metodo set risulterà in un errore, almeno quando lo si utilizza in una funzione cloud Node.js.


2

Diverso dalle risposte sopra menzionate. Questo lo farà. Utilizzando Angular 5 e AngularFire2. oppure usa firebase.firestore () invece di this.afs

  // say you have have the following object and 
  // database structure as you mentioned in your post
  data = { who: "third@test.com", 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
    });
 }  

1

Considera John Doe un documento piuttosto che una raccolta

Dagli una raccolta di cose e coseSharedWithOthers

Quindi puoi mappare e interrogare le cose condivise di John Doe nella raccolta parallela thingsSharedWithOthers.

proprietary: "John Doe"(a document)

things(collection of John's things documents)

thingsSharedWithOthers(collection of John's things being shared with others):
[thingId]:
    {who: "first@test.com", when:timestamp}
    {who: "another@test.com", when:timestamp}

then set thingsSharedWithOthers

firebase.firestore()
.collection('thingsSharedWithOthers')
.set(
{ [thingId]:{ who: "third@test.com", when: new Date() } },
{ merge: true }
)

0

Se qualcuno sta cercando la soluzione SDK firestore Java per aggiungere elementi nel campo array:

List<String> list = java.util.Arrays.asList("A", "B");
Object[] fieldsToUpdate = list.toArray();
DocumentReference docRef = getCollection().document("docId");
docRef.update(fieldName, FieldValue.arrayUnion(fieldsToUpdate));

Per eliminare gli elementi dall'utente dell'array: FieldValue.arrayRemove()


0
addToCart(docId: string, prodId: string): Promise<void> {
    return this.baseAngularFirestore.collection('carts').doc(docId).update({
        products:
        firestore.FieldValue.arrayUnion({
            productId: prodId,
            qty: 1
        }),
    });
}

1
Per favore, spiega la tua risposta.
Jenea Vranceanu

In effetti, si prega di leggere stackoverflow.com/help/how-to-answer
jasie

-1

È così che l'ho fatto funzionare. Spero che ti aiuti tutti poiché lo vedo come una soluzione migliore. Qui voglio cambiare lo stato dell'attività da open (close = false) a close (close = true), mantenendo tutto il resto lo stesso nell'oggetto.

closeTask(arrayIndex) {
        let tasks = this.lead.activityTasks;

        const updatedTask = {
          name: tasks[arrayIndex].name,
          start: tasks[arrayIndex].start,
          dueDate:tasks[arrayIndex].dueDate,
          close: true, // This is what I am changing.
        };
        tasks[arrayIndex] = updatedTask;

        const data = {
          activityTasks: tasks
        };

        this.leadService.updateLeadData(data, this.lead.id);
    }

ed ecco il servizio che effettivamente lo aggiorna

 public updateLeadData(updateData, leadId) {
    const leadRef: AngularFirestoreDocument<LeadModel> = this.afs.doc(
      `leads/${leadId}`);

return leadRef.update(updateData);
}

Ciao. Tenere presente che questo approccio presenta molti problemi e rischi di perdita / danneggiamento / rigidità dei dati a causa di un uso sfortunato di array e di un "aggiornamento" non incrementale. Non memorizzare le attività in un array. Mettili invece in una collezione.
KarolDepka

Scusa, ho dovuto votare in negativo perché contiene cattive pratiche.
KarolDepka
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.