Wie aktualisiere ich ein "Array von Objekten" mit Firestore?


104

Ich versuche gerade Firestore und stecke bei etwas sehr Einfachem fest: "Aktualisieren eines Arrays (auch bekannt als Unterdokument)".

Meine DB-Struktur ist super einfach. Beispielsweise:

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

Ich versuche (ohne Erfolg), neue Datensätze in eine shareWithReihe von Objekten zu verschieben.

Ich habe es versucht:

// 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() }] })

Keiner funktioniert. Diese Abfragen überschreiben mein Array.

Die Antwort mag einfach sein, aber ich konnte sie nicht finden ...

Antworten:


70

Bearbeiten 13.08.2008 : Es werden jetzt native Array-Vorgänge im Cloud Firestore unterstützt. Siehe Dougs Antwort unten.


Derzeit gibt es keine Möglichkeit, ein einzelnes Array-Element im Cloud Firestore zu aktualisieren (oder ein einzelnes Element hinzuzufügen / zu entfernen).

Dieser Code hier:

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

Dies besagt, dass das Dokument proprietary/docIDso eingestellt werden soll, dass sharedWith = [{ who: "third@test.com", when: new Date() }keine vorhandenen Dokumenteigenschaften beeinflusst werden. Es ist dem von update()Ihnen bereitgestellten set()Anruf sehr ähnlich, jedoch ist der Anruf beim Erstellen des Dokuments vorhanden, wenn es nicht vorhanden ist, während der update()Anruf fehlschlägt.

Sie haben also zwei Möglichkeiten, um das zu erreichen, was Sie wollen.

Option 1 - Stellen Sie das gesamte Array ein

Rufen Sie set()mit dem gesamten Inhalt des Arrays auf, wobei zuerst die aktuellen Daten aus der Datenbank gelesen werden müssen. Wenn Sie über gleichzeitige Updates besorgt sind, können Sie dies alles in einer Transaktion tun.

Option 2 - Verwenden Sie eine Untersammlung

Sie können sharedWitheine Untersammlung des Hauptdokuments erstellen. Dann würde das Hinzufügen eines einzelnen Elements folgendermaßen aussehen:

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

Dies bringt natürlich neue Einschränkungen mit sich. Sie können Dokumente nicht danach abfragen, mit wem sie geteilt werden, und Sie können das Dokument und alle sharedWithDaten nicht in einem einzigen Vorgang abrufen.


9
Das ist so frustrierend ... aber danke, dass du mich wissen lässt, dass ich nicht verrückt werde.
ItJustWerks

49
Dies ist ein großer Nachteil, Google muss es so schnell wie möglich beheben.
Sajith Mantharath

3
Die Antwort von @ DougGalante zeigt an, dass dies behoben wurde. Verwenden Sie die arrayUnionMethode.
Quicklikerabbit

149

Firestore verfügt jetzt über zwei Funktionen, mit denen Sie ein Array aktualisieren können, ohne das gesamte Objekt neu schreiben zu müssen.

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

Aktualisieren Sie Elemente in einem Array

Wenn Ihr Dokument ein Array-Feld enthält, können Sie mit arrayUnion () und arrayRemove () Elemente hinzufügen und entfernen. arrayUnion () fügt einem Array Elemente hinzu, aber nur Elemente, die noch nicht vorhanden sind. arrayRemove () entfernt alle Instanzen jedes angegebenen Elements.


55
Gibt es eine Möglichkeit, einen bestimmten Index aus dem Array zu aktualisieren?
Artur Carvalho

1
Wie verwende ich diese Array-Update-Funktion mit "react-native-firebase"? (Ich kann dies nicht in offiziellen Dokumenten von
React

4
@ ArturCarvalho Nein, der Grund, warum in diesem Video erklärt wird youtube.com/…
Adam

@ArturCarvalho Haben Sie eine Lösung gefunden, um einen bestimmten Index aus dem Array zu aktualisieren?
Yogendra Patel

3
Für diejenigen, die dies auf dem Client tun müssen, verwenden Sie "import * as firebase from 'firebase / app';" dann "firebase.firestore.FieldValue.arrayUnion (NEW_ELEMENT)"
michelepatrassi

15

Sie können eine Transaktion ( https://firebase.google.com/docs/firestore/manage-data/transactions ) verwenden, um das Array abzurufen, darauf zu übertragen und dann das Dokument zu aktualisieren:

    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
Bei if-Anweisung sollten Sie dies ändern, da Ihnen das documentReferenceHinzufügen userRefwie gezeigt fehlt : transaction.set(userRef, { bookings: [booking] });
Ilir Hushi

8

Hier ist das neueste Beispiel aus der Firestore-Dokumentation:

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, dies fügt tatsächlich ein neues String-Element "Greater_virginia" zu einem vorhandenen Array "Regionen" hinzu. Ich habe es erfolgreich getestet und definitiv kein "Objekt" hinzugefügt. Es stimmt mit der Frage überein, wie angegeben: "Neue Datensätze pushen".
Veeresh Devireddy


3

Um auf Sam Sterns Antwort aufzubauen , gibt es auch eine dritte Option, die mir die Arbeit erleichtert hat, nämlich die Verwendung einer von Google als Map bezeichneten Karte, bei der es sich im Wesentlichen um ein Wörterbuch handelt.

Ich denke, ein Wörterbuch ist weitaus besser für den Anwendungsfall, den Sie beschreiben. Normalerweise verwende ich Arrays für Dinge, die nicht wirklich zu stark aktualisiert werden, daher sind sie mehr oder weniger statisch. Aber für Dinge, die viel geschrieben werden, insbesondere Werte, die für Felder aktualisiert werden müssen, die mit etwas anderem in der Datenbank verknüpft sind, erweisen sich Wörterbücher als viel einfacher zu pflegen und zu bearbeiten.

Für Ihren speziellen Fall würde die DB-Struktur also folgendermaßen aussehen:

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

Auf diese Weise können Sie Folgendes tun:

var whoEmail = 'first@test.com';

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

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

Der Grund für die Definition des Objekts als Variable besteht darin, dass die Verwendung 'sharedWith.' + whoEmail + '.when'direkt in der set-Methode zu einem Fehler führt, zumindest wenn es in einer Node.js-Cloud-Funktion verwendet wird.


2

Andere als die oben genannten Antworten. Das wird es tun. Verwenden von Angular 5 und AngularFire2. oder verwenden Sie firebase.firestore () anstelle von 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

Betrachten Sie John Doe eher als ein Dokument als als eine Sammlung

Geben Sie ihm eine Sammlung von Dingen und Dingen, die mit anderen geteilt werden

Anschließend können Sie die gemeinsam genutzten Dinge von John Doe in dieser parallelen Sammlung von thingsSharedWithOthers zuordnen und abfragen.

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

Wenn jemand nach einer Java Firestore SDK-Lösung sucht, um Elemente im Array-Feld hinzuzufügen:

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

So löschen Sie Elemente aus dem Array-Benutzer: FieldValue.arrayRemove()


-1

So habe ich es zum Laufen gebracht. Hoffe, es hilft euch allen, da ich es als eine viel bessere Lösung sehe. Hier möchte ich den Aufgabenstatus von open (close = false) in close (close = true) ändern, während alles andere im Objekt gleich bleibt.

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

und hier ist der Dienst, der es tatsächlich aktualisiert

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

return leadRef.update(updateData);
}

Hallo. Bitte beachten Sie, dass dieser Ansatz viele Probleme aufweist und Datenverlust / -beschädigung / -flexibilität aufgrund einer unglücklichen Verwendung von Arrays und eines nicht inkrementellen "Updates" birgt. Sie sollten die Aufgaben nicht in einem Array speichern. Haben Sie sie stattdessen in einer Sammlung.
KarolDepka

Entschuldigung, ich musste abstimmen, weil es schlechte Praktiken enthält.
KarolDepka
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.