【问题标题】:How to perform an arrayUnion and an arrayRemove in a single query using Firebase's Firestore?如何使用 Firebase 的 Firestore 在单个查询中执行 arrayUnion 和 arrayRemove?
【发布时间】:2020-07-10 09:53:08
【问题描述】:

我正在使用 firestore 来更新我的对象上的数组。 我在documentation 上发现我可以执行数组联合和删除,这很棒,这是文档中给出的示例:

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 个单独的查询,这使我的应用程序在数据库调用方面的成本增加了一倍,因此我想将它们分组到一个查询中,如下所示:

var washingtonRef = db.collection("cities").doc("DC");

washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia"),
    regions: firebase.firestore.FieldValue.arrayRemove("east_coast")
});

不幸的是,这不起作用,只有最后一个命令被执行。

有没有办法让它工作?

【问题讨论】:

    标签: javascript firebase google-cloud-firestore


    【解决方案1】:

    简介

    编辑:这个答案最初是在现代模块化 SDK 发布之前编写的,它已经更新以涵盖这个新的 SDK 以及当时可用的旧命名空间 SDK。对于新项目,请使用模块化 SDK。

    FieldValue 对象

    您的组合指令不起作用的原因(除了语法错误)是因为 FieldValue 对象的定义方式。

    假设您定义了以下可在您的update() 调用中使用的对象:

    // Firebase Namespaced SDK (v8 & older)
    // import firebase as appropriate
    
    const myArrayUnion = firebase.firestore.FieldValue.arrayUnion("greater_virginia")
    const myArrayRemove = firebase.firestore.FieldValue.arrayRemove("east_coast")
    
    // Firebase Modular SDK (v9+)
    import { arrayUnion, arrayRemove } from "firebase/firestore";
    
    const myArrayUnion = arrayUnion("greater_virginia")
    const myArrayRemove = arrayRemove("east_coast")
    

    返回的对象是 FieldValue 类的实现,相当于

    const myArrayUnion = {
      _method: "FieldValue.arrayUnion",
      _elements: ["greater_virginia"]
    }
    
    const myArrayRemove = {
      _method: "FieldValue.arrayRemove",
      _elements: ["east_coast"]
    }
    

    然后,根据_method 的值,适当的字段转换指令为serialised using this code 并发送到Firestore API。因为操作是根据_method 的值切换的,所以在一条指令上只能执行arrayUnionarrayRemove 中的一个。

    arrayUnionarrayRemove

    arrayUnionarrayRemove 都可以接受多个参数,将每个参数添加到上面显示的内部 _elements 数组中。

    因此要将"value1""value2" 同时添加到指定字段,您可以使用:

    // Firebase Namespaced SDK (v8 & older)
    firebase.firestore.FieldValue.arrayUnion("value1", "value2");
    
    // Firebase Modular SDK (v9+)
    arrayUnion("value1", "value2");
    

    要将一组项目同时添加到指定字段,您可以使用:

    // Firebase Namespaced SDK (v8 & older)
    const addedElements = ["greater_virginia", "east_coast", "central"];
    
    firebase.firestore.FieldValue.arrayUnion.apply(null, addedElements);
    // or
    firebase.firestore.FieldValue.arrayUnion(...addedElements);
    
    // Firebase Modular SDK (v9+)
    const addedElements = ["greater_virginia", "east_coast", "central"];
    
    arrayUnion.apply(null, addedElements);
    // or
    arrayUnion(...addedElements);
    

    选项

    因此,您有三个选项,按易用性和推荐排序:

    选项 1:批量写入

    使用batched write 将允许您将指令一起写入数据库,如果任一部分失败,则不会更改任何内容。

    // Firebase Namespaced SDK (v8 & older)
    // import firebase as appropriate
    
    var db = firebase.firestore();
    var washingtonRef = db.collection("cities").doc("DC");
    var batch = db.batch();
    
    batch.update(washingtonRef, {regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")});
    batch.update(washingtonRef, {regions: firebase.firestore.FieldValue.arrayRemove("east_coast")});
    
    batch.commit()
      .then(() => console.log('Success!'))
      .catch(err => console.error('Failed!', err));
    
    // Firebase Modular SDK (v9+)
    import { getFirestore, arrayRemove, arrayUnion, doc, writeBatch } from "firebase/firestore";
    
    const db = getFirestore();
    const washingtonRef = doc(db, "cities", "DC");
    const batch = writeBatch(db);
    
    batch.update(washingtonRef, {regions: arrayUnion("greater_virginia")});
    batch.update(washingtonRef, {regions: arrayRemove("east_coast")});
    
    batch.commit()
      .then(() => console.log('Success!'))
      .catch(err => console.error('Failed!', err));
    

    注意:每个批量写入最多可包含 500 次写入。

    在最初编写此答案时(此后已被删除),每个转换指令(arrayUnionarrayRemoveincrementserverTimestamp)将计为 2 次操作到此限制,因为读取和写入都被计算在内,这意味着一个批次只能使用 250 个转换。

    选项 2:反转数组

    这种特殊的数据结构让人想起实时数据库,并且在引入 arrayUnionarrayRemove 操作之前。

    一般的前提是在将数组上传到数据库之前对其进行转换。

    const originalArr = ["greater_virginia", "east_coast", "central"]
    

    被反转并存储为

    const keyedObjectOfArr = {
      "greater_virginia": 1
      "east_coast": 1,
      "central": 1
    }
    

    上述结果可以使用:

    const keyedObjectOfArr = originalArr.reduce((acc, v) => (acc[v] = 1, acc), {});
    

    并恢复正常使用

    const originalArr = Object.keys(keyedObjectOfArr);
    

    然后,当您想应用联合/删除时,您将使用以下内容:

    // Firebase Namespaced SDK (v8 & older)
    // import firebase as appropriate
    
    /**
     * Creates (or adds to the given object) changes to be committed to the database.
     * 
     * Note: Add operations will override remove operations if they exist in both arrays.
     * 
     * @param fieldPath The path to the 'array' field to modify
     * @param addedArray (optional) Elements to be added to the field
     * @param removedArray (optional) Elements to be removed from the field
     * @param changes (optional) A previous changes object for chaining
     */
    function addArrayChanges(fieldPath, addedArray = [], removedArray = [], changes = {}) {
      var fvDelete = firebase.firestore.FieldValue.delete();
      removedElements.forEach(e => changes[fieldPath + '.' + e] = fvDelete);
      addedElements.forEach(e => changes[fieldPath + '.' + e] = 1);
      return changes;
    }
    
    var washingtonRef = db.collection("cities").doc("DC");
    var addedElements = ["greater_virginia"];
    var removedElements = ["east_coast"];
    
    var changes = addArrayChanges("regions", addedElements, removedElements);
    
    washingtonRef.update(changes)
      .then(() => console.log('Success!'))
      .catch(err => console.error('Failed!', err));
    
    // Firebase Modular SDK (v9+)
    import { getFirestore, arrayRemove, arrayUnion, deleteField, doc, updateDoc, writeBatch } from "firebase/firestore";
    
    /**
     * Creates (or adds to the given object) changes to be committed to the database.
     * 
     * Note: Add operations will override remove operations if they exist in both arrays.
     * 
     * @param fieldPath The path to the 'array' field to modify
     * @param addedArray (optional) Elements to be added to the field
     * @param removedArray (optional) Elements to be removed from the field
     * @param changes (optional) A previous changes object for chaining
     */
    function addArrayChanges(fieldPath, addedArray = [], removedArray = [], changes = {}) {
      const fvDelete = deleteField();
      removedElements.forEach(e => changes[fieldPath + '.' + e] = fvDelete);
      addedElements.forEach(e => changes[fieldPath + '.' + e] = 1);
      return changes;
    }
    
    const db = getFirestore();
    const washingtonRef = doc(db, "cities", "DC");
    const addedElements = ["greater_virginia"];
    const removedElements = ["east_coast"];
    
    const changes = addArrayChanges("regions", addedElements, removedElements);
    
    updateDoc(washingtonRef, changes)
      .then(() => console.log('Success!'))
      .catch(err => console.error('Failed!', err));
    

    选项 3:交易

    虽然此方法是一种选择,但对于此用例来说是不切实际的,建议不要使用。 This blog post 涵盖 Firebase 实时数据库中的数组,但这些问题也适用于大规模单个文档的内容。

    请求功能

    Firestore API 中可能有空间同时支持添加和删除数组条目,因为它们在序列化层是分开的。

    interface FieldTransform {
      fieldPath?: string;
      setToServerValue?: FieldTransformSetToServerValue;
      appendMissingElements?: ArrayValue;
      removeAllFromArray?: ArrayValue;
      increment?: Value;
    }
    

    所以你也可以提交Feature Request 看看会发生什么。

    【讨论】:

    • @raphadko 我已经添加了更多信息的自助餐。
    猜你喜欢
    • 2018-03-20
    • 1970-01-01
    • 1970-01-01
    • 2021-08-06
    • 2020-11-26
    • 1970-01-01
    • 2019-07-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多