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

189

我目前正在尝试使用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() }] })

没有一个有效,这些查询会覆盖我的数组。

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


2
嘿,你解决了吗?我还是找不到答案。 - Yash Jain
对于Android来说,更新Firestore中的对象数组就像这篇文章如何在Firestore中更新对象数组?所述一样简单。 - Alex Mamo
18个回答

221

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


149
有没有办法更新数组中特定索引的值? - Artur Carvalho
2
如何在“react-native-firebase”中使用数组更新功能?(我在“react-native-firebase”的官方文档中找不到此功能) - kernelman
4
对于需要在客户端执行此操作的人,请使用“import * as firebase from 'firebase/app';”,然后使用“firebase.firestore.FieldValue.arrayUnion(NEW_ELEMENT)” - michelepatrassi
10
如果有一种方法可以使用特定ID更新数组中的项目,那就太好了。就像使用arrayUnion一样,但是需要设置为merge:true。目前需要执行两个操作来删除数组项,然后再使用新数据添加它。 - MadMac
显示剩余2条评论

117

编辑于 2018 年 08 月 13 日: Cloud Firestore 现在支持原生数组操作。请参考下面的Doug's answer


目前无法在 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数据。


13
这真让人沮丧……但感谢你让我知道我没有变疯。 - ItJustWerks
71
这是一个很大的缺点,Google必须尽快解决它。 - Sajith Mantharath
7
@DougGalante的回答表明此问题已得到修复。请使用arrayUnion方法。 - quicklikerabbit
这位仁兄,请看一下我的问题 https://stackoverflow.com/questions/73427668/how-to-update-an-array-of-objects-in-firestore?noredirect=1#comment129679860_73427668 ,这真的让我很沮丧,我还是不明白。 - Kennedy Mathinya

38

这是来自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")
});

2
@nifCody,这确实会将一个新的字符串元素“greater_virginia”添加到现有的数组“regions”中。我已经成功测试过了,绝对不是添加“对象”。它与所述问题保持同步:“推送新记录”。 - Veeresh Devireddy

24

您可以使用事务 (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);
    });

2
在if语句中,您应该进行如下更改,因为您缺少了'documentReference',请按以下方式添加'userRef': transaction.set(userRef, { bookings: [booking] }); - Ilir Hushi
这将重写整个数组。 - Ayush Kumar

10

1
如果每个示例都有相关的代码,那个链接会很有用。 - tazboy

6

Sam Stern's answer的基础上,还有第三种选项,对我来说更容易的方法是使用谷歌所称的“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);

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

5
如果你有一个数组,想要使用它来更新现有的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 },
);

希望这能有所帮助 :)

1
虽然这可能回答了问题,但如果可能的话,您应该[编辑]您的答案,包括简短说明如何这个代码块回答了问题。这有助于提供上下文,并使您的答案对未来的读者更有用。 - Hoppeduppeanut
1
@Hoppeduppeanut 是的,你说得对。我肯定接受没有为我的答案添加解释而受到批评。我已经编辑了我的答案。希望现在能更好地帮助你。 - Benyam

3

3

我知道这已经很老了,但是为了帮助新手解决这个问题

firebase V9 提供了一种使用 arrayUnion 和 arrayRemove 解决方法。

await updateDoc(documentRef, {
    proprietary: arrayUnion( { sharedWith: [{ who: "third@test.com", when: new Date() }] }
});

点击此处获取更详细的解释


3
addToCart(docId: string, prodId: string): Promise<void> {
    return this.baseAngularFirestore.collection('carts').doc(docId).update({
        products:
        firestore.FieldValue.arrayUnion({
            productId: prodId,
            qty: 1
        }),
    });
}

1
请解释你的答案。 - Jenea Vranceanu
请确实阅读 https://stackoverflow.com/help/how-to-answer。 - jasie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接