Як оновити "масив об'єктів" за допомогою Firestore?


104

Зараз я пробую 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() }] })

Жоден не працює. Ці запити перезаписують мій масив.

Відповідь може бути простою, але я не міг її знайти ...

Відповіді:


71

Редагувати 13.08.2018 : Тепер у Cloud Firestore підтримується робота з власними масивами. Дивіться відповідь Дага нижче.


Наразі в Cloud Firestore немає можливості оновити окремий елемент масиву (або додати / видалити один елемент).

Цей код тут:

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

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

Отже, у вас є два варіанти досягнення бажаного.

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

Виклик set()із усім вмістом масиву, для чого потрібно спочатку прочитати поточні дані з БД. Якщо вас турбують паралельні оновлення, ви можете зробити все це під час транзакції.

Варіант 2 - Використовуйте підколекцію

Ви можете зробити sharedWithпідколекцію основного документа. Тоді додавання одного елемента буде виглядати так:

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

Звичайно, це має нові обмеження. Ви не зможете запитувати документи на основі того, з ким їм надано спільний доступ, а також не зможете отримати документ та всі sharedWithдані за одну операцію.


9
Це так розчаровує ... але дякую, що повідомили, що я не збожеволію.
ItJustWerks

50
це великий недолік, Google повинен це виправити якомога швидше.
Саджіт Мантарат

3
Відповідь @ DougGalante вказує, що це було виправлено. Використовуйте arrayUnionметод.
quicklikerabbit

152

Тепер 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 () видаляє всі екземпляри кожного заданого елемента.


56
Чи є спосіб оновити певний індекс із масиву?
Артур Карвальо,

1
Як використовувати цю функцію оновлення масиву з функцією "response-native-firebase"? (Я не можу знайти це в офіційних документах React-native-Firebase)
kernelman

4
@ArturCarvalho Ні, причина, чому пояснюється у цьому відео youtube.com/…
Адам

@ArturCarvalho Ви знайшли якесь рішення для оновлення певного індексу з масиву?
Йогендра Патель

3
для тих, хто повинен це зробити на клієнті, використовуйте "import * as firebase from 'firebase / app';" потім "firebase.firestore.FieldValue.arrayUnion (NEW_ELEMENT)"
michelepatrassi

15

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

1
У заяві if ви повинні змінити його на це, оскільки вам не вистачає documentReferenceдоповнення, userRefяк показано: transaction.set(userRef, { bookings: [booking] });
Ilir

8

Ось останній приклад з документації 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, це дійсно додасть новий елемент рядка "більша_віргінія" до існуючого масиву "регіони". Я перевірив його успішно, і точно не додаю "об'єкт". Це синхронізується із запитанням, як зазначено: "натискати нові записи".
Веереш Девіредді

7

Вибачте, пізно на вечірку, але Firestore вирішив це ще в серпні 2018 року, тому якщо ви все ще шукаєте це тут, це всі проблеми, вирішені щодо масивів.

https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html Офіційний допис у блозі

array-contains, arrayRemove, arrayUnion для перевірки, видалення та оновлення масивів. Сподіваюся, це допомагає.


3

Щоб спиратись на відповідь Сема Стерна , є також 3-й варіант, який полегшив мені роботу, і це використання того, що Google називає Map, що, по суті, є словником.

Я думаю, що словник набагато кращий для випадку використання, який ви описуєте. Зазвичай я використовую масиви для речей, які насправді не надто оновлюються, тому вони більш-менш статичні. Але для матеріалів, про які багато пишеться, зокрема для значень, які потрібно оновити для полів, пов’язаних з чимось іншим у базі даних, словники виявляються набагато простішими в обслуговуванні та роботі.

Отже, для вашого конкретного випадку структура БД буде виглядати так:

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.


2

Крім відповідей, згаданих вище. Це зробить це. Використання 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
    });
 }  

1

Вважайте Джона Доу документом, а не колекцією

Подаруйте йому колекцію речей та речей, які поділяються з іншими

Потім ви можете зіставити та запитати спільні речі Джона Доу у цій паралельній колекції 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

Якщо хтось шукає рішення 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()



-1

Ось як я змусив це працювати. Сподіваюся, це допоможе вам усім, оскільки я бачу це набагато кращим рішенням. Тут я хочу змінити статус завдання з відкритого (закрити = хибне) на закрите (закрити = істинне), зберігаючи все інше незмінним в об'єкті.

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

Привіт. Зверніть увагу, що такий підхід має багато проблем та ризикує втрату / пошкодження / негнучкість даних через невдале використання масивів та неінкрементальне "оновлення". Не слід зберігати завдання в масиві. Нехай вони будуть у колекції.
KarolDepka

Вибачте, мені довелося проголосувати проти, оскільки в ньому є погані практики.
KarolDepka
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.