Jak zaktualizować „tablicę obiektów” za pomocą Firestore?

104

Obecnie próbuję Firestore i utknąłem na czymś bardzo prostym: „aktualizowanie tablicy (czyli dokumentu podrzędnego)”.

Moja struktura bazy danych jest super prosta. Na przykład:

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

Próbuję (bez powodzenia) wypchnąć nowe rekordy do shareWithtablicy obiektów.

Próbowałem:

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

Żadne nie działa. Te zapytania zastępują moją tablicę.

Odpowiedź może być prosta, ale nie mogłem jej znaleźć ...

nerotulip
źródło

Odpowiedzi:

71

Edycja 13.08.2018 : W Cloud Firestore dostępna jest teraz obsługa natywnych operacji na macierzach. Zobacz odpowiedź Douga poniżej.


Obecnie nie ma możliwości zaktualizowania pojedynczego elementu tablicy (lub dodania / usunięcia pojedynczego elementu) w Cloud Firestore.

Ten kod tutaj:

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

To mówi, aby ustawić dokument w proprietary/docIDtaki sposób, sharedWith = [{ who: "[email protected]", when: new Date() }aby nie wpływał na żadne istniejące właściwości dokumentu. Jest to bardzo podobne do update()wywołania, które podałeś, jednak set()wywołanie z tworzeniem dokumentu, jeśli nie istnieje, a update()wywołanie zakończy się niepowodzeniem.

Masz więc dwie możliwości osiągnięcia tego, co chcesz.

Opcja 1 - Ustaw całą tablicę

Wywołaj set()całą zawartość tablicy, co będzie wymagało najpierw odczytania bieżących danych z bazy danych. Jeśli obawiasz się jednoczesnych aktualizacji, możesz to wszystko zrobić w transakcji.

Opcja 2 - użyj kolekcji podrzędnej

Możesz utworzyć sharedWithpodkolekcję głównego dokumentu. Wtedy dodanie pojedynczego elementu wyglądałoby tak:

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

Oczywiście wiąże się to z nowymi ograniczeniami. Nie byłbyś w stanie przeszukiwać dokumentów na podstawie tego, komu są one udostępnione, ani nie byłbyś w stanie uzyskać dokumentu i wszystkich sharedWithdanych w jednej operacji.

Sam Stern
źródło
9
To takie frustrujące ... ale dzięki, że dałeś mi znać, że nie wariuję.
ItJustWerks
52
to jest duża wada, Google musi to naprawić jak najszybciej.
Sajith Mantharath
3
Odpowiedź @ DougGalante wskazuje, że problem został rozwiązany. Użyj arrayUnionmetody.
quicklikerabbit
152

Firestore ma teraz dwie funkcje, które pozwalają aktualizować tablicę bez ponownego pisania całej rzeczy.

Link: https://firebase.google.com/docs/firestore/manage-data/add-data , w szczególności https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array

Zaktualizuj elementy w tablicy

Jeśli dokument zawiera pole tablicy, możesz użyć funkcji arrayUnion () i arrayRemove (), aby dodawać i usuwać elementy. arrayUnion () dodaje elementy do tablicy, ale tylko elementy, które nie są jeszcze obecne. arrayRemove () usuwa wszystkie wystąpienia każdego podanego elementu.

Doug Galante
źródło
57
Czy istnieje sposób na zaktualizowanie określonego indeksu z tablicy?
Artur Carvalho
1
Jak korzystać z tej funkcji aktualizacji macierzy w „React-native-firebase”? (Nie mogę tego znaleźć w oficjalnych dokumentach
React
4
@ArturCarvalho Nie, powód jest wyjaśniony w tym filmie youtube.com/…
Adam
3
kto musi to zrobić na kliencie, użyj "importu * jako firebase z 'firebase / app';" następnie „firebase.firestore.FieldValue.arrayUnion (NEW_ELEMENT)”
michelepatrassi
1
Byłoby wspaniale, gdyby istniał sposób na zaktualizowanie elementu w tablicy o określonym identyfikatorze. Podobnie jak arrayUnion, ale ze scaleniem: true. W tej chwili wymaga 2 operacji, aby usunąć element tablicy, a następnie dodać go ponownie z nowymi danymi.
MadMac
15

Możesz użyć transakcji ( https://firebase.google.com/docs/firestore/manage-data/transactions ), aby pobrać tablicę, wypchnąć ją, a następnie zaktualizować dokument:

    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);
    });
Gabriel McCallin
źródło
1
W oświadczeniu if należy to zmienić na to, ponieważ brakuje documentReferencedodania, userRefjak pokazano: transaction.set(userRef, { bookings: [booking] });
Ilir Hushi
8

Oto najnowszy przykład z dokumentacji 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")
});
Veeresh Devireddy
źródło
@nifCody, to rzeczywiście doda nowy element ciągu „Greater_virginia” do istniejącej tablicy „regiony”. Przetestowałem to pomyślnie i zdecydowanie nie dodając „obiektu”. Jest to zsynchronizowane z zadanym pytaniem: „pchnij nowe rekordy”.
Veeresh Devireddy
3

Aby oprzeć się na odpowiedzi Sama Sterna , istnieje również trzecia opcja co czyniło rzeczy łatwiejsze dla mnie i że jest za pomocą tego, co nazywamy mapą Google, która jest w zasadzie słownika.

Myślę, że słownik jest znacznie lepszy dla opisywanego przypadku użycia. Zwykle używam tablic do rzeczy, które nie są zbytnio aktualizowane, więc są mniej więcej statyczne. Ale w przypadku rzeczy, które są często pisane, w szczególności wartości, które wymagają aktualizacji dla pól, które są powiązane z czymś innym w bazie danych, słowniki okazują się znacznie łatwiejsze w utrzymaniu i pracy z nimi.

Więc w twoim konkretnym przypadku struktura DB wyglądałaby następująco:

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

Umożliwi to wykonanie następujących czynności:

var whoEmail = '[email protected]';

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

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

Powodem definiowania obiektu jako zmiennej jest użycie 'sharedWith.' + whoEmail + '.when' bezpośrednio w metodzie set spowoduje błąd, przynajmniej podczas używania go w funkcji chmury Node.js.

Horea
źródło
2

Poza odpowiedziami wymienionymi powyżej. To wystarczy. Korzystanie z Angular 5 i AngularFire2. lub użyj firebase.firestore () zamiast this.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
    });
 }  
Jassi
źródło
1

Potraktuj Johna Doe jako dokument, a nie kolekcję

Daj mu kolekcję rzeczy i rzeczy udostępnianych przez innych

Następnie możesz mapować i wyszukiwać współdzielone elementy Johna Doe w tej równoległej kolekcji 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: "[email protected]", when:timestamp}
    {who: "[email protected]", when:timestamp}

then set thingsSharedWithOthers

firebase.firestore()
.collection('thingsSharedWithOthers')
.set(
{ [thingId]:{ who: "[email protected]", when: new Date() } },
{ merge: true }
)
Richard Taylor-Kenny
źródło
0

Jeśli ktoś szuka rozwiązania Java Firestore SDK, aby dodać elementy w polu tablicy:

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

Aby usunąć elementy z użytkownika tablicy: FieldValue.arrayRemove()

A_01
źródło
0
addToCart(docId: string, prodId: string): Promise<void> {
    return this.baseAngularFirestore.collection('carts').doc(docId).update({
        products:
        firestore.FieldValue.arrayUnion({
            productId: prodId,
            qty: 1
        }),
    });
}
Blazet
źródło
1
Proszę, wyjaśnij swoją odpowiedź.
Jenea Vranceanu
Rzeczywiście, przeczytaj stackoverflow.com/help/how-to-answer
jasie