firebase 这是事务中firestore增量的预期行为吗?

sqyvllje  于 2023-03-31  发布在  其他
关注(0)|答案(1)|浏览(154)
  • 编辑:原来我的代码中有一个bug,SDK没有问题。在同一个事务中对同一个文档使用两个或多个原子增量操作没有问题。更多上下文请阅读接受的答案及其注解。*

我使用firestore根据childrenSituation字段值计算 users 集合中有多少用户。该字段可以是 HAS_KIDSDOESNT_HAVE_KIDSPERIODICALLY_HAS_KIDS
为此,我在一个名为 aggregated 的集合中有一个 data 文档。这个文档有三个数值字段,每当我使用原子增加来创建或删除用户时,我都会增加或减少它们。但是当涉及到更新用户时,我必须在同一个事务中更新同一文档中的两个数值,这导致将其中一个字段设置为-1,而不是减少一个单位,而不将另一个设置为任何值。
我用increment字段值这样做:

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不能直接获取类示例并更新文档。对于聚合文档,我使用原子增加来更新这两个字段:一个为正,另一个为负。

预期行为

在我的单元测试中,我在预测试中创建了一个用户,因此这是在调用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。对吗?

lmyy7pcs

lmyy7pcs1#

很难准确地理解代码发生了什么,因为我们无法重现完全相同的情况(我们只能看到一个函数)。
然而,我用一个简单的HTML页面,JavaScript和JS SDK(不是TypeScript,也不是Admin SDK)做了一个测试,它似乎工作正常:

  • Firestore Transaction可以在一个原子操作中执行多个写操作,如doc;
  • 如果aggregated/data文档中不存在该字段,则使用正确的值正确创建该字段(即,在不存在的字段上递增的情况下为1,或在不存在的字段上递减的情况下为-1)。

您可以在浏览器中本地打开以下HTML页面并对其进行测试,然后查找代码中可能需要更改的内容。我只是在用户文档中写入了dummy date,因为我并不真正理解您对this._serializer.serializeUser(user)的操作。您需要将config对象适配到您的Firebase项目。
请注意,您不需要获取文档以获取其DocumentReference:因此,无需执行const userDocSnap = await t.get(...)userDocSnap.ref。只需执行t.set(this._usersCollection.doc(user.id), this._serializer.serializeUser(user), {merge: true});,因为您已经知道此文档的DocumentReference。对于aggregatedDataDocSnap也是如此:不用去找医生了

<!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>

相关问题