Как обновить "массив объектов" с помощью Firestore?

В настоящее время я пробую Firestore, и я застрял на чем-то очень простом: "обновление массива (так называемый поддокумент)".

Моя структура БД очень проста. Например:

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

Я пытаюсь (безуспешно) вставить новые записи в массив объектов shareWith.

Я пробовал:

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

Ничего не работает. Эти запросы перезаписывают мой массив.

Ответ может быть простым, но я не могу его найти...

Ответ 1

Редактировать 08/13/2018: теперь есть поддержка операций с собственными массивами в Cloud Firestore. См. Ответ Дуга ниже.


В настоящее время нет способа обновить один элемент массива (или добавить/удалить один элемент) в Cloud Firestore.

Этот код здесь:

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

Это говорит о том, чтобы установить документ на proprietary/docID, так что sharedWith = [{ who: "[email protected]", when: new Date() } но не влияет на какие-либо существующие свойства документа. Он очень похож на вызов update() вы предоставили, однако вызов set() с созданием документа, если он не существует, пока вызов update() не будет выполнен.

Таким образом, у вас есть два варианта достижения желаемого.

Вариант 1 - установка всего массива

Call set() со всем содержимым массива, для чего потребуется сначала считывать текущие данные из базы данных. Если вас беспокоит одновременное обновление, вы можете сделать все это в транзакции.

Вариант 2. Использование подмножества

Вы можете сделать sharedWith субколлекцией основного документа. Затем добавление одного элемента будет выглядеть так:

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

Конечно, это связано с новыми ограничениями. Вы не сможете запрашивать документы на основе того, с кем они совместно используются, и вы не сможете получить документ и все данные sharedWith за одну операцию.

Ответ 2

Теперь Firestore имеет две функции, которые позволяют вам обновлять массив без повторной записи всего объекта.

Ссылка: https://firebase.google.com/docs/firestore/manage-data/add-data, в частности https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array

Обновление элементов в массиве

Если ваш документ содержит поле массива, вы можете использовать функции arrayUnion() и arrayRemove() для добавления и удаления элементов. arrayUnion() добавляет элементы в массив, но только элементы, которые уже не присутствуют. arrayRemove() удаляет все экземпляры каждого заданного элемента.

Ответ 3

Вы можете использовать транзакцию (https://firebase.google.com/docs/firestore/manage-data/transactions), чтобы получить массив, нажать на него и затем обновить документ:

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

Ответ 4

Извините за позднюю вечеринку, но Firestore решил это еще в августе 2018 года, так что, если вы все еще ищете это здесь, это все проблемы, связанные с массивами.

https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.htmlОфициальное сообщение в блоге

array-contains, arrayRemove, arrayUnion для проверки, удаления и обновления массивов. Надеюсь, это поможет.

Ответ 5

Чтобы опираться на ответ Сэма Стерна, есть также третий вариант, который облегчил мне работу и это то, что Google вызывает карту, которая по сути является словарем.

Я думаю, что словарь намного лучше для используемого вами варианта использования. Обычно я использую массивы для вещей, которые не слишком сильно обновляются, поэтому они более или менее статичны. Но для материалов, которые написаны много, особенно значений, которые необходимо обновить для полей, которые связаны с чем-то другим в базе данных, словари оказываются намного проще поддерживать и работать.

Итак, для вашего конкретного случая структура БД будет выглядеть так:

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

Это позволит вам сделать следующее:

var whoEmail = '[email protected]';

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

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

Причиной для определения объекта как переменной является то, что использование 'sharedWith.' + whoEmail + '.when' непосредственно в методе set приведет к ошибке, по крайней мере, при ее использовании в облачной функции Node.js.

Ответ 6

Кроме ответов, упомянутых выше. Это сделает это. Использование Angular 5 и AngularFire2. или используйте firebase.firestore() вместо 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
    });
 }  

Ответ 7

Вот как я заработал. Надеюсь, это поможет вам всем, так как я считаю это лучшим решением. Здесь я хочу изменить статус задачи из open (close = false), чтобы закрыть (close = true), сохраняя при этом все остальное в объекте.

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

и вот сервис, который фактически обновляет его

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

return leadRef.update(updateData);
}

Ответ 8

Рассмотрим Джон Доу документ, а не сборник

Дайте ему коллекцию вещей и вещей.

Затем вы можете сопоставлять и запрашивать общие вещи Джона Доу в этой параллельной коллекции thingsSharedWithOthers.

proprietary: "John Doe"(a document)

things(collection of John things documents)

thingsSharedWithOthers(collection of John 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 }
)

Ответ 9

Если кто-то ищет Java-решение FireStore SDK для добавления элементов в поле массива:

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

Чтобы удалить элементы из массива пользователя: FieldValue.arrayRemove()