编辑:原来我的代码中有一个bug, SDK没有问题。在同一事务中对同一文档使用两个或多个原子增量操作是没有问题的。要了解更多上下文,请阅读接受的答案及其评论。
我使用firestore来计数有多少用户在我的用户集合取决于他们的childrenSituation
字段值。该字段可以是HAS_KIDS、DOESNT_HAVE_KIDS或PERIODICALLY_HAS_KIDS。
我用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),
});
});
}
我将更新的用户和之前的childsituation传递给这个方法[我这样做是为了保存读取,但我现在看到这是不必要的,因为我正在从DB获取用户文档]。要更新用户,请使用` ` serialize ` `;这是因为Firestore不能直接获取类实例并更新文档。对于聚合文档,我使用原子递增更新两个字段:一个为正,另一个为负。
期望行为
在我的单元测试中,我在预测试中创建了一个用户,因此这是在调用updateUser
方法之前聚合/数据文档的内容:
{
DOESNT_HAVE_KIDS: 1,
}
然后调用updateUser
,我期望aggregate/data的内容为:
{
HAS_KIDS: 1,
DOESNT_HAVE_KIDS: 0
}
实际情况
在我提到的相同的预测试条件下,执行updateUser后,聚合/数据的内容如下:
{
DOESNT_HAVE_KIDS: -1
}
这是Firestore的预期行为,还是我应该在github中打开一个问题?如果Firestore不兼容在同一个写操作中有多个原子增量,那么至少应该将其设置为0。对吧?
很难准确理解代码中发生了什么,因为我们无法重现完全相同的情况(我们只看到一个函数)。
然而,我已经做了一个简单的HTML页面,JavaScript和JS SDK(不是TypeScript和不是Admin SDK)的测试,它似乎工作正常:
- Firestore Transaction可以在一个原子操作中执行多个写操作,如文档中所述;
- 如果
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});
,因为你已经知道这个文档的DocumentReference
。aggregatedDataDocSnap
也一样:不需要获取doc.
<!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>