仅当数据不存在时如何将数据添加到firestore?

时间:2021-06-27 13:06:01

标签: javascript node.js json google-cloud-firestore

我在 firestore 中有一个名为 Products 的集合。

产品 JSON:

{ 
  Id1:
  {"product": "product1"},
  Id2:
  {"product": "product2"},
  Id3:
  {"product": "product3"},
}

我想使用 JSON 和 Node Js 向其中添加数据,这样我就不会添加任何重复数据。我是 NodeJS 和异步方法的新手。所以我想问一下,如果我做对了:

const data = require("./data.json");
for (var key in data) {
   var query = firestore.collection(collectionKey);
   var product = data[key]["product"];
   query.where("product", '==', product).get().then((res) => {
      if (!res.empty) {
         console.log(product + " exists");
      } else {
          firestore.collection(collectionKey).doc(key).set(data[key]).then((res) => {
             console.log(product + " successfully written!");
          .catch((error) => {
               console.error("Error writing document: ", error);
            });
         });
      }
   });
}

console.log 只打印最后一个产品,我不知道哪些产品已经存在,哪些已成功编写?

编辑:

node script
Console Output:

H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
H1358 exists
..
.

data.json 包含 58 个不同的产品,即产品 ID 依次为:H1301..H1358。

2 个答案:

答案 0 :(得分:0)

tl:dr; 如果您希望产品(product1product2product3)在集合中是唯一的,您必须使用这些产品作为文档的 ID。< /strong>

不幸的是,您当前的代码中存在竞争条件。在您运行查询和实际编写文档之间,另一个客户端可能最终编写了一个具有相同 product 值的文档,就像您也在编写它一样。

     Client 1                 DATABASE                  Client 2
        +                    +--------+                    +
        |  q: where p="p1"   |        |                    |
        |+------------------>|        |                    |
        |                    |        |                    |
        |  No results        |        |                    |
        |<------------------+|        |                    |
        |                    |        |   q: where p="p1"  |
        |                    |        |<------------------+|
        |                    |        |                    |
        |                    |        |   No results.      |
        |                    |        |+------------------>|
        | Create: { p: "p1"} |        |                    |
        |+------------------>|        |                    |
        |                    |        |                    |
        |      OK            |        |                    |
        |<------------------+|        |                    |
        |                    |        | Create: { p: "p1"} |
        |                    |        |<------------------+|
        |                    |        |                    |
        |                    |        | OK                 |
        |                    |        |+------------------>|
        |                    |        |                    |
        +                    +--------+                    +

当您知道要创建的文档的 ID 时,您可以 use a transaction 首先获取然后创建该文档,这确保没有两个客户端可以最终创建该文档,作为其中之一他们将被迫重试,然后失败。

但是由于您无法在事务中包含查询本身,因此您无法将整个“找到具有此值的文档,如果没有则创建它”作为原子操作进行。

以事务方式执行此操作的唯一方法是将产品值设为文档的键。一旦你这样做了,你就不再需要查询了,并且可以使用事务来“获取文档,如果它还不存在,则创建它”。

     Client 1                 DATABASE                  Client 2
        +                    +--------+                    +
        | Start transaction  |        |                    |
        |+------------------>|        |                    |
        |                    |        | Start transaction  |
        |                    |        |<------------------+|
        | get document p1    |        |                    |
        |+------------------>|        |                    |
        |                    |        |                    |
        |  No result         |        |                    |
        |<------------------+|        |                    |
        |                    |        | get document p1    |
        |                    |        |<------------------+|
        |                    |        |                    |
        |                    |        |  No result         |
        |                    |        |+------------------>|
        | create document p1 |        |                    |
        |+------------------>|        |                    |
        |                    |        |                    |
        |      OK            |        |                    |
        |<------------------+|        |                    |
        |                    |        | create document p1 |
        |                    |        |<------------------+|
        |                    |        |                    |
        |                    |        |  OK                |
        |                    |        |+------------------>|
        | Commit transaction |        |                    |
        |+------------------>|        |                    |
        |                    |        |                    |
        |      OK            |        |                    |
        |<------------------+|        |                    |
        |                    |        | Commit transaction |
        |                    |        |<------------------+|
        |                    |        |                    |
        |                    |        |  Reject            |
        |                    |        |+------------------>|
        |                    |        |                    |
        +                    +--------+                    +

您还需要在服务器上 use security rules 拒绝尝试重新创建相同文档的写入,因为恶意用户可能会在没有您的事务代码的情况下写入数据库。


更新:Doug 指出,您实际上可以在与服务器端/管理 SDK 的事务中包含查询。

答案 1 :(得分:0)

啊,我现在看到问题了。到回调触发时,key 的值已更新多次 - 所有这些都记录了最终值。

这里的解决方案是使用所谓的 immediately invoked function expression,但我发现这里使用命名函数更容易阅读。

const data = require("./data.json");
var query = firestore.collection(collectionKey);
var logIfDocExists = function(key, product) {
   query.where("product", '==', product).get().then((res) => {
      if (!res.empty) {
         console.log(product + " exists");
      } else {
          firestore.collection(collectionKey).doc(key).set(data[key]).then((res) => {
             console.log(product + " successfully written!");
          .catch((error) => {
               console.error("Error writing document: ", error);
            });
         });
      }
   });
};
for (var key in data) {
   var product = data[key]["product"];
   logIfDocExists(key, product);
}

这里的技巧是 logIfDocExists(key, product)key 捕获为调用堆栈上的一个单独值,然后确保您在异步回调中记录正确的 key 值。


我实际上刚刚意识到另一种更简单的方法来捕获 key 的值,那就是在原始代码中使用 let 而不是 varlet 定义了一个块级变量,因此循环的每次迭代都会有自己唯一的键值。