问题
I'm trying to run a transaction with a variable number of read operations. I put the read () operations before than update ().
Reading the Firestore doc on https://cloud.google.com/firestore/docs/manage-data/transactions
"A transaction consists of any number of get() operations followed by any number of write operations such as set(), update(), or delete()"
And
When using transactions, note that:
- Read operations must come before write operations.
- A function calling a transaction (transaction function) might run more than once if a current edit affects a document that the transaction reads.
- Transaction functions should not directly modify application state.
But is not provided an implementation. When I try to run the code below, I get that the transaction function is runned more time and then I obtain an exception. But if I try with only one get all goes 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');
});
});
回答1:
The Firestore doc doesn't say this, but the answer is hidden in the API reference: https://cloud.google.com/nodejs/docs/reference/firestore/0.13.x/Transaction?authuser=0#getAll
You can use Transaction.getAll()
instead of Transaction.get()
to get multiple documents. Your example will be:
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'));
回答2:
I couldn't figure out how to do this in pure Typescript, but I was able to find a JavaScript example that uses promises, so I adapted that to fit my needs. It seems to be working correctly, however when I run my function rapidly (by clicking on a button in rapid succession) I get console errors that read POST https://firestore.googleapis.com/v1beta1/projects/myprojectname/databases/(default)/documents:commit 400 ()
. I am unclear on whether those are errors I should be worried about, or if they're simply a a result of the transaction retrying. I posted my own question about that, and am hopeful to get some answers on it. In the meantime, here is the code that I came up with:
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);
});
}
回答3:
I was facing the same problem and decided to use a combination of a batched write and "normal" reads.
The decision was guided by the fact that I needed to make many reads that did not rely on each other. At first I used a method similar to the one proposed by Derrick above, but it proved not sustainable for may reads.
The code dictates that every loop is blocking to the next one.
What I did was to batch all the reads to run in parallel with Promise.all
The disadvantage of this is that you dont take advantage of transaction features, but since the field I was iterested in was not changing, it made sense
Here's my sample code
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()
})
This has proven to be more efficient for many reads, while remaining safe since the fields I'm interested in dont change
来源:https://stackoverflow.com/questions/47664012/firestore-transaction-with-multiple-get