这是交易中 firestore 增量的预期行为吗?

Is this the expected behavior of firestore increments inside transactions?

编辑:原来我的代码中有一个错误,SDK 没有问题。在同一个事务中对同一个文档使用两个或多个原子增量操作是没有问题的。有关更多上下文,请阅读已接受的答案及其评论。


我正在使用 firestore 来计算我的 users 集合中有多少用户,具体取决于他们的 childrenSituation 字段值。该字段可以是 HAS_KIDSDOESNT_HAVE_KIDSPERIODICALLY_HAS_KIDS.

为此,我在名为 aggregated 的集合中有一个 data 文档。该文档具有三个数字字段,每当我使用原子增加创建或删除用户时,我都会增加或减少它们。但是当涉及到更新用户时,我必须在同一个事务中更新同一个文档中的两个数值,这导致将其中一个字段设置为 -1 而不是在一个单元中减少它,并且不将另一个设置为任何值。

我用 increment FieldValue 这样做:

async updateUser(user: User, userChildrenSituationBeforeUpdate: ChildrenSituationEnum): Promise<void> {
    return await this._firestore.runTransaction(async (t) => {
      const userDocSnap = await t.get(this._usersCollection.doc(user.id));
      const aggregatedDataDocSnap = await t.get(this._agregateDataCollection.doc('data'));

      t.set(userDocSnap.ref, this._serializer.serializeUser(user), {merge: true});
      t.update(aggregatedDataDocSnap.ref, {
        [user.childrenSituation]: this._admin.firestore.FieldValue.increment(1),
        [userChildrenSituationBeforeUpdate]: this._admin.firestore.FieldValue.increment(-1),
      });
    });
  }

我将更新的用户和之前的 childrenSituation 传递给此方法 [我这样做是为了保存阅读,但我现在发现这是不必要的,因为我正在从数据库中获取用户文档]。为了更新用户,我将其“序列化”,因为 Firestore 无法直接获取 class 个实例并更新文档。对于聚合文档,我使用原子增加更新了两个字段:一个是正的,另一个是负的。

预期行为

在我的单元测试中,我在预测试中创建了一个用户,因此这是调用 updateUser 方法之前 aggregated/data 文档的内容:

{
    DOESNT_HAVE_KIDS: 1,
}

然后调用 updateUser我预计 aggregated/data 的内容为:

{
    HAS_KIDS: 1,
    DOESNT_HAVE_KIDS: 0
}

实际发生了什么

同样的预测试条件,执行updateUseraggregated/data的内容为:

{
    DOESNT_HAVE_KIDS: -1
}

这是 Firestore 的预期行为,还是我应该在 github 中提出问题?如果 Firestore 不兼容在同一写入操作中有多个原子增加,至少应该将其设置为 0。对吗?

很难准确理解您的代码发生了什么,因为我们无法重现完全相同的情况(我们只看到一个函数)。

但是,我用一个简单的 HTML 页面、JavaScript 和 JS SDK(不是 TypeScript,也不是 Admin SDK)进行了测试,它似乎工作正常:

  • Firestore 事务可以在一个原子操作中执行多个写入,如 doc;
  • 中所述
  • 如果该字段在 aggregated/data 文档中不存在,则会使用正确的值正确创建它(即 1,如果在不存在的字段上递增,或 -1,如果在不存在的字段上递减) -现有字段)。

您可以在本地浏览器中打开以下 HTML 页面并对其进行测试,找出您的代码中可能需要更改的内容。我刚刚给用户文档写了虚拟日期,因为我真的不明白你用 this._serializer.serializeUser(user) 做了什么。您需要使配置对象适应您的 Firebase 项目。

请注意,您无需获取文档即可获取其 DocumentReference:因此无需执行 const userDocSnap = await t.get(...)userDocSnap.ref。只需执行 t.set(this._usersCollection.doc(user.id), this._serializer.serializeUser(user), {merge: true});,因为您已经知道此文档的 DocumentReferenceaggregatedDataDocSnap 相同:无需获取文档。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Test SO</title>

    <script src="https://www.gstatic.com/firebasejs/8.2.5/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.2.5/firebase-firestore.js"></script>

    <script>
      // Initialize Firebase
      var config = {
        projectId: '......',
      };

      firebase.initializeApp(config);
      const db = firebase.firestore();

      async function updateUser(user, userChildrenSituationBeforeUpdate) {
        return await db.runTransaction(async (t) => {
          t.set(
            db.collection('users').doc(user.id),
            { foo: 'bar' },
            { merge: true }
          );
          t.update(db.collection('aggregated').doc('data'), {
            [user.childrenSituation]: firebase.firestore.FieldValue.increment(
              1
            ),
            [userChildrenSituationBeforeUpdate]: firebase.firestore.FieldValue.increment(
              -1
            ),
          });
        });
      }

      updateUser({ id: '2', childrenSituation: 'HAS_KIDS' }, 'DOESNT_HAVE_KIDS')
        .then(() => {
          console.log('OK');
        })
        .catch((error) => {
          console.log(error);
        });
    </script>
  </head>
  <body></body>
</html>