Firestoreで「オブジェクトの配列」を更新する方法は?


104

私は現在Firestoreを試していますが、「配列(別名サブドキュメント)の更新」という非常に単純なもので立ち往生しています。

私のDB構造は非常に単純です。例えば:

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

2018年8月13日編集: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()呼び出しが失敗するときに存在しない場合は、ドキュメントを作成する呼び出しで作成します。

だからあなたが望むものを達成するための2つのオプションがあります。

オプション1-アレイ全体を設定する

set()配列の内容全体で呼び出します。最初にDBから現在のデータを読み取る必要があります。同時更新が心配な場合は、トランザクションでこれをすべて実行できます。

オプション2-サブコレクションを使用する

sharedWithメイン文書のサブコレクションを作成できます。次に、単一のアイテムを追加すると、次のようになります。

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

もちろん、これには新しい制限があります。ドキュメントの共有相手に基づいてドキュメントをクエリすることはできませんsharedWith。また、1回の操作でドキュメントとすべてのデータを取得することもできません。


9
これはとてもイライラします...しかし、私が狂っていないことを知らせてくれてありがとう。
ItJustWerks 2017年

50
これは大きな欠点です。Googleはできるだけ早く修正する必要があります。
Sajith Mantharath 2017

3
@DougGalanteの回答は、これが修正されたことを示しています。arrayUnionメソッドを使用します。
quicklikerabbit 2018

151

Firestoreには2つの関数があり、全部を書き直さなくても配列を更新できます。

リンク: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
配列から特定のインデックスを更新する方法はありますか?
Artur Carvalho

1
この配列更新機能を「react-native-firebase」で使用するにはどうすればよいですか?(react-native-
firebaseの

4
@ArturCarvalhoいいえ、この動画で説明している理由youtube.com/...
アダム

@ArturCarvalho配列から特定のインデックスを更新するための解決策を見つけましたか?
Yogendra Patel

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 Hushi

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、これは実際に新しい文字列要素「greater_virginia」を既存の配列「regions」に追加します。私はそれを首尾よくテストしました、そして、確実に「オブジェクト」を加えません。これは、「新しいレコードをプッシュする」という質問と同期しています。
Veeresh Devireddy

7

パーティーに遅れて申し訳ありませんが、Firestoreは2018年8月にその方法で解決しました。それでもここでそれを探しているのであれば、アレイに関するすべての問題が解決されます。

https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html 公式ブログの投稿

array-contains、arrayRemove、arrayUnionは、配列のチェック、削除、更新を行います。それが役に立てば幸い。


3

サム・スターンの答えに基づいて構築するために、私にとって物事をより簡単にする3番目のオプションもあり、それはGoogleが本質的に辞書であるマップと呼ぶものを使用しています。

あなたが説明しているユースケースには、辞書がはるかに優れていると思います。私は通常、あまり更新されないものに配列を使用するので、それらは多かれ少なかれ静的です。しかし、頻繁に書かれるもの、特にデータベース内の他の何かにリンクされているフィールドに対して更新する必要がある値の場合、辞書は維持および操作がはるかに簡単であることがわかります。

したがって、特定のケースでは、DB構造は次のようになります。

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'、少なくともNode.jsクラウド関数で使用する場合、setメソッドで直接使用するとエラーが発生するためです。


2

上記の回答以外。これでできます。 Angular 5とAngularFire2を使用する。またはthis.afsの代わりにfirebase.firestore()を使用します

  // 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

John Doeをコレクションではなくドキュメントと考える

モノとモノのコレクションを与える

次に、その並列のthingsSharedWithOthersコレクションでJohn Doeの共有されたものをマッピングしてクエリできます。

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

これは私がそれを機能させた方法です。私はそれがより良い解決策であると考えているので、それがあなたたちすべてを助けることを願っています。ここでは、オブジェクトのその他すべてを同じに保ちながら、タスクのステータスをopen(close = false)からclose(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);
}

こんにちは。このアプローチには多くの問題があり、配列の不幸な使用とインクリメンタルでない「更新」が原因でデータの損失/破損/柔軟性にリスクがあることに注意してください。タスクを配列に格納しないでください。代わりにコレクションに入れてください。
KarolDepka

申し訳ありませんが、悪い習慣があるため、反対票を投じなければなりませんでした。
KarolDepka
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.