Сейчас я пробую Firestore, и я застрял на очень простом: «обновление массива (также известного как поддокумент)».

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

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

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

Пробовал:

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

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

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

Ответы (15)

Изменить 13.08.2018: теперь в Cloud Firestore есть поддержка операций с собственными массивами. См. ответ Дуга ниже.


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

Вот этот код:

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

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

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

Вариант 1 - Установить весь массив

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

Option 2 - Use a subcollection

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

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

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

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

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

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

Считайте Джона Доу документом, а не коллекцией

Отдать ему коллекцию вещей и вещей SharedWithOthers

Затем вы можете сопоставить и запросить общие вещи Джона Доу в этой параллельной коллекции 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 }
)

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

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

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

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

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

var whoEmail = 'first@test.com';

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.

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

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

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

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

Если документ содержит вложенный объект в форме массива, нотацию .dot можно использовать для ссылки и обновления вложенных полей. Пример Node.js:

const users = {
  name: 'Tom',
  surname: 'Smith',
  favorites: {
    sport: 'tennis',
    color: 'red',
    subject: 'math'
  }
};

const update = await db.collection('users').doc('Tom').update({
  'favorites.sport': 'snowboard'
});

или пример Android sdk:

db.collection("users").document("Tom")
        .update(
               'favorites.sport': 'snowboard'
        );

Мы можем использовать arrayUnion ({}) метод, чтобы добиться этого.

Попробуйте это:

collectionRef.doc (ID) .update ({
    sharedWith: admin.firestore.FieldValue.arrayUnion ({
       кто: "first@test.com",
       когда: новая дата ()
    })
});

Документацию можно найти здесь: https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array

Кроме ответов, упомянутых выше. Это сделает это. Использование 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: "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
    });
 }  
addToCart(docId: string, prodId: string): Promise {
    return this.baseAngularFirestore.collection('carts').doc(docId).update({
        products:
        firestore.FieldValue.arrayUnion({
            productId: prodId,
            qty: 1
        }),
    });
}

Если вы хотите обновить массив в документе firebase. Вы можете это сделать.

    var documentRef = db.collection("Your collection name").doc("Your doc name")

    documentRef.update({
yourArrayName: firebase.firestore.FieldValue.arrayUnion("The Value you want to enter")});

Вы можете использовать транзакцию (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);
    });

Хотя firebase.firestore.FieldValue.arrayUnion () предоставляет решение для обновления массива в firestore, в то же время необходимо использовать {merge: true}. Если вы не используете {merge: true}, он удалит все остальные поля в документе при обновлении с новым значением. Вот рабочий код для обновления массива без потери данных в справочном документе с помощью метода .set ():


const docRef = firebase.firestore (). collection ("your_collection_name"). doc ("your_doc_id");

docRef.set ({yourArrayField: firebase.firestore.FieldValue.arrayUnion ("value_to_add")}, {merge: true});

Вот последний пример из документации Firestore:

firebase.firestore.FieldValue.ArrayUnion

var WashingtonRef = db.collection ("города"). Doc ("DC");

// Атомарно добавляем новый регион в поле массива "region".
WashingtonRef.update ({
    регионы: firebase.firestore.FieldValue.arrayUnion ("большая_вирджиния")
});

// Атомарно удаляем регион из поля массива "region".
WashingtonRef.update ({
    регионы: firebase.firestore.FieldValue.arrayRemove ("east_coast")
});

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

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

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

# Edit (добавить пояснение :)) скажем, у вас есть массив, которым вы хотите обновить существующее поле документа firestore. Вы можете использовать set (yourData, {merge: true}) передавая setOptions (второй параметр в функции set) с {merge: true} необходимо для объединения изменений вместо перезапись. вот что об этом говорится в официальной документации

Объект параметров, который настраивает поведение вызовов set () в DocumentReference, WriteBatch и Transaction. Эти вызовы можно настроить для выполнения гранулярных слияний вместо полной перезаписи целевых документов, предоставив SetOptions с merge: true.

вы можете использовать это

const yourNewArray = [{who: "first@test.com", when:timestamp}
{who: "another@test.com", when:timestamp}]    


collectionRef.doc(docId).set(
  {
    proprietary: "jhon",
    sharedWith: firebase.firestore.FieldValue.arrayUnion(...yourNewArray),
  },
  { merge: true },
);

надеюсь, что это поможет :)

2022 WebDevInsider