【问题标题】:How to add data to firestore only if it doesn't exist?仅当数据不存在时如何将数据添加到firestore?
【发布时间】:2021-06-27 13:06:01
【问题描述】:

我在 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。

【问题讨论】:

  • 您正在为每个键(Id1Id2Id3)发起一个查询,并且您的代码应该为每个查询记录一些内容。这没有发生吗?您可以编辑您的问题以显示日志输出吗?
  • 添加了控制台输出。

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


【解决方案1】:

啊,我现在看到了问题。到回调触发时,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 定义了一个块级变量,因此循环的每次迭代都会有自己唯一的键值。

【讨论】:

  • 对数据使用 foreach 箭头函数,对我有用。
【解决方案2】:

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

不幸的是,您当前的代码中存在竞争条件。在您运行查询和实际编写文档之间,另一个客户端最终可能会编写具有相同 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 指出您实际上可以在与服务器端/Admin SDK 的事务中包含查询。

【讨论】:

  • 可以用 Promise 完成吗?而且只有一个客户。我正在使用它来填充只读数据。
  • Promise 是一种客户端机制,用于处理调用的异步性质。此问题与来自多个客户端的数据库访问的并发性有关。我添加了一些图表,希望能更好地解释这一点。
  • 我理解你的意思,但我是说只有一个客户可以写入数据库。所以比赛条件不会成为问题。
  • 在这种情况下:您共享的代码有什么问题?
  • Console.log 只打印最后一个产品,不知道哪些产品已经存在,哪些写成功了?
猜你喜欢
  • 2016-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-27
  • 1970-01-01
  • 2022-01-05
相关资源
最近更新 更多