如何使用Firestore更新"对象数组"?

ner*_*lip 70 javascript arrays object firebase google-cloud-firestore

我正在尝试Firestore,而且我遇到了一些非常简单的事情:"更新一个数组(也就是一个子文档)".

我的DB结构非常简单.例如:

proprietary: "John Doe",
sharedWith:
  [
    {who: "first@test.com", when:timestamp},
    {who: "another@test.com", when:timestamp},
  ],
Run Code Online (Sandbox Code Playgroud)

我正在尝试(没有成功)将新记录推送到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() }] })
Run Code Online (Sandbox Code Playgroud)

没有用.这些查询会覆盖我的数组.

答案可能很简单,但我找不到......

谢谢

Dou*_*nte 91

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()删除每个给定元素的所有实例.

  • 有没有办法从阵列更新特定的索引? (23认同)
  • 这如何与一组对象一起使用? (13认同)
  • 现在应该是正确的答案 (3认同)
  • 对于需要在客户端执行此操作的人,请使用“import * as firebase from 'firebase/app';” 然后“firebase.firestore.FieldValue.arrayUnion(NEW_ELEMENT)” (3认同)
  • 如果有一种方法可以更新具有特定 id 的数组中的项目,那就太好了。类似于 arrayUnion 但具有 merge: true。目前,需要执行 2 次操作来删除数组项,然后使用新数据再次添加它。 (3认同)
  • 如何通过“react-native-firebase”使用此数组更新功能?(我在react-native-firebase的官方文档中找不到这个) (2认同)
  • @ArturCarvalho不,这个视频中解释了原因https://www.youtube.com/watch?v=o7d5Zeic63s&feature=youtu.be&list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ&t=525 (2认同)

Sam*_*ern 51

编辑08/13/2018:现在支持Cloud Firestore中的本机阵列操作.请参阅下面的道格答案.


目前无法在Cloud Firestore中更新单个数组元素(或添加/删除单个元素).

这段代码在这里:

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

这是说,在设置文件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() })
Run Code Online (Sandbox Code Playgroud)

当然,这带来了新的局限性.您将无法根据共享对象来查询文档,也无法sharedWith在单个操作中获取文档和所有数据.

  • 这是一个很大的缺点,谷歌必须尽快解决它. (34认同)
  • 这真令人沮丧......但是感谢让我知道我不会发疯. (7认同)
  • @DougGalante的回答表明此问题已得到解决。使用`arrayUnion`方法。 (2认同)

Vee*_*ddy 25

这是 Firestore 文档中的最新示例:

firebase.firestore.FieldValue。阵列联合

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")
});
Run Code Online (Sandbox Code Playgroud)

  • @nifCody,这确实会向现有数组“regions”添加一个新的字符串元素“greater_virginia”。我已经测试成功了,绝对没有添加“对象”。它与所述问题同步:“推送新记录”。 (2认同)

小智 13

您可以使用交易(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);
    });
Run Code Online (Sandbox Code Playgroud)

  • 在 if 语句中,您应该将其更改为这样,因为您缺少 `documentReference` 添加 `userRef`,如下所示: `transaction.set(userRef, { bookings: [booking] });` (2认同)

Gur*_*uru 8

抱歉,聚会迟到了,但 Firestore 早在 2018 年 8 月就解决了,所以如果您仍在寻找它,那么所有关于数组的问题都已解决。

https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html官方博文

array-contains、arrayRemove、arrayUnion 用于检查、删除和更新数组。希望能帮助到你。


Hor*_*rea 6

Sam Stern 的回答为基础,还有第三个选项让我更轻松,那就是使用 Google 所谓的地图,它本质上是一本字典。

我认为字典对于您描述的用例要好得多。我通常将数组用于没有真正更新太多的东西,所以它们或多或少是静态的。但是对于需要大量编写的内容,特别是需要为链接到数据库中其他内容的字段更新的值,事实证明字典更易于维护和使用。

因此,对于您的具体情况,数据库结构如下所示:

proprietary: "John Doe"
sharedWith:{
  whoEmail1: {when: timestamp},
  whoEmail2: {when: timestamp}
}
Run Code Online (Sandbox Code Playgroud)

这将允许您执行以下操作:

var whoEmail = 'first@test.com';

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

firebase.firestore()
.collection('proprietary')
.doc(docID)
.update(sharedObject);
Run Code Online (Sandbox Code Playgroud)

将对象定义为变量的原因是,'sharedWith.' + whoEmail + '.when'直接在 set 方法中使用会导致错误,至少在 Node.js 云函数中使用时如此。


Ben*_*yam 5

#Edit(添加说明:))假设您有一个数组要用来更新现有的 firestore 文档字段。您可以使用set(yourData, {merge: true} )传递 setOptions(set 函数中的第二个参数) 来{merge: true}合并更改而不是覆盖。这是官方文档的相关内容

一个选项对象,用于配置 DocumentReference、WriteBatch 和 Transaction 中 set() 调用的行为。可以将这些调用配置为执行粒度合并,而不是通过提供带有 merge: true 的 SetOptions 来完全覆盖目标文档。

你可以用这个

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 },
);
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助 :)