我正在尝试运行具有可变数量的读取操作的事务。 我把 read() 操作放在 update() 之前。
阅读 Firestore 文档 https://cloud.google.com/firestore/docs/manage-data/transactions
“事务由任意数量的 get() 操作组成,后跟任意数量的写入操作,例如 set()、update() 或 delete()”
还有
使用交易时请注意:
- 读操作必须先于写操作。
- 如果当前编辑影响了文档,则调用事务(事务函数)的函数可能会运行多次 事务读取。
- 事务函数不应直接修改应用程序状态。
但它没有提供实现。 当我尝试运行下面的代码时,我发现事务函数运行了更多次,然后出现异常。 但如果我只尝试使用一个
get
一切都会OK。
const reservationCol = this.db.firestore.collection('reservations');
return this.db.firestore.runTransaction(t => {
return Promise.all([
t.get(reservationCol.doc('id1')),
t.get(reservationCol.doc('id2'))
]).then((responses) => {
let found = false;
responses.forEach(resp => {
if (resp.exists)
{
found = true;
}
});
if (!found)
{
entity.id = 'id1';
t.set(reservationCol.doc(entity.id), entity);
return Promise.resolve('ok');
}
else
{
return Promise.reject('exist');
}
});
});
Firestore 文档没有这么说,但答案隐藏在 API 参考中:https://cloud.google.com/nodejs/docs/reference/firestore/0.13.x/Transaction?authuser=0#getAll
您可以使用
Transaction.getAll()
代替 Transaction.get()
来获取多个文档。你的例子是:
const reservationCol = this.db.firestore.collection('reservations');
return this.db.firestore.runTransaction(t => {
return t.getAll(reservationCol.doc('id1'), reservationCol.doc('id2'))
.then(docs => {
const id1 = docs[0];
const id2 = docs[1];
if (!(id1.exists && id2.exists)) {
// do stuff
} else {
// throw error
}
})
}).then(() => console.log('Transaction succeeded'));
我无法弄清楚如何在纯 Typescript 中执行此操作,但我能够找到一个使用 Promise 的 JavaScript 示例,因此我对其进行了调整以满足我的需求。它似乎工作正常,但是当我快速运行我的函数(通过快速连续单击按钮)时,我收到控制台错误,内容为
POST https://firestore.googleapis.com/v1beta1/projects/myprojectname/databases/(default)/documents:commit 400 ()
。我不清楚这些是否是我应该担心的错误,或者它们是否只是事务重试的结果。我发布了我自己的问题,并希望得到一些答案。与此同时,这是我想出的代码:
async vote(username, recipeId, direction) {
let value;
if ( direction == 'up' ) {
value = 1;
}
if ( direction == 'down' ) {
value = -1;
}
// assemble vote object to be recorded in votes collection
const voteObj: Vote = { username: username, recipeId: recipeId , value: value };
// get references to both vote and recipe documents
const voteDocRef = this.afs.doc(`votes/${username}_${recipeId}`).ref;
const recipeDocRef = this.afs.doc('recipes/' + recipeId).ref;
await this.afs.firestore.runTransaction( async t => {
const voteDoc = await t.get(voteDocRef);
const recipeDoc = await t.get(recipeDocRef);
const currentRecipeScore = await recipeDoc.get('score');
if (!voteDoc.exists) {
// This is a new vote, so add it to the votes collection
// and apply its value to the recipe's score
t.set(voteDocRef, voteObj);
t.update(recipeDocRef, { score: (currentRecipeScore + value) });
} else {
const voteData = voteDoc.data();
if ( voteData.value == value ) {
// existing vote is the same as the button that was pressed, so delete
// the vote document and revert the vote from the recipe's score
t.delete(voteDocRef);
t.update(recipeDocRef, { score: (currentRecipeScore - value) });
} else {
// existing vote is the opposite of the one pressed, so update the
// vote doc, then apply it to the recipe's score by doubling it.
// For example, if the current score is 1 and the user reverses their
// +1 vote by pressing -1, we apply -2 so the score will become -1.
t.set(voteDocRef, voteObj);
t.update(recipeDocRef, { score: (currentRecipeScore + (value*2))});
}
}
return Promise.resolve(true);
});
}
我面临着同样的问题,并决定使用批量写入和“正常”读取的组合。 我做出这个决定的原因是我需要进行许多互不依赖的阅读。起初我使用了类似于上面 Derrick 提出的方法,但事实证明它对于可能的读取来说是不可持续的。 该代码规定每个循环都会阻塞到下一个循环。 我所做的是将所有读取批处理以与
Promise.all
并行运行
这样做的缺点是你没有利用交易功能,但由于我感兴趣的领域没有改变,所以这是有道理的
这是我的示例代码
const batch = firestore().batch()
const readPromises = invoiceValues.map(val => {
return orderCollection(omcId).where(<query field>, '<query operation>', <query>).get()
})
return Promise.all(readPromises).then(orderDocs => {
//Perform batch operations here
return batch.commit()
})
事实证明,这对于许多读取来说更有效,同时保持安全,因为我感兴趣的字段不会改变