I want to run a transaction to update data in the Cloud Firestore using cloud_firestore_odm.
This code works fine:
usersRef
.doc('foo_id')
.update(
name: 'John',
);
But this one doesn't. I'm doing something wrong, can anyone tell me how to properly do it?
final transaction = await FirebaseFirestore.instance.runTransaction((_) async => _);
usersRef
.doc('foo_id')
.transactionUpdate(
transaction,
name: 'John',
);
Due to how the ODM works, the syntax for using transactions using the Firestore ODM is slightly different.
Instead of:
await FirebaseFirestore.instance.runTransaction((transaction) async {
transaction.update(usersRef.doc('id'), {'age': 42});
});
You should do:
await FirebaseFirestore.instance.runTransaction((transaction) async {
usersRef.doc('id').transactionUpdate(transaction, age: 42);
});
Basically, the transaction vs "reference" are swapped. But as a benefit, the transaction object is fully typed.
The same logic applies for any other transaction method.
Try this:
await FirebaseFirestore.instance((transaction) async {
await transaction.update(usersRef.doc('foo_id'),{
'name' : 'John'
});
});
Related
In a node backend we're using prisma to access a mongo db, and for automated integration testing I'd like to reset the db at the beginning of each test. Prisma's roadmap has planned a reset feature for the mongo connector but they're not there yet, so I need to implement the reset myself.
For my purposes it would be sufficient to drop all the documents in the collections -- I don't need to reset schemas, indexes, etc. What's the best way to do this?
I've found an approach that seems to work using prisma's runCommandRaw as follows, but I'm unsure of its limitations or failure modes. (And it seems like there should be a more prisma-ish way to iterate through the models/collections, but I haven't found it yet -- perhaps I'm overlooking something.)
Any improvements, alternatives, or warnings about this approach would be very helpful.
import { PrismaClient } from '#prisma/client';
const getAllCollectionNames = async (prisma: PrismaClient): Promise<string[]> => {
// This seems to return a cursor-like object, implying that there may
// be more results. If that's the case, can we use $runCommandRaw to
// paginate through the complete set of collections?
const queryResult = await prisma.$runCommandRaw({
listCollections: 1.0,
authorizedCollections: true,
nameOnly: true,
});
// #ts-ignore
return queryResult.cursor.firstBatch.map((coll: any) => coll.name);
};
const deleteAllFromCollection = async (prisma: PrismaClient, name: string): Promise<void> => {
await prisma.$runCommandRaw({
delete: name,
deletes: [
{
q: {},
limit: 0, // (no limit, delete everything))
},
],
});
};
export const truncateAllCollections = async (prisma: PrismaClient): Promise<void> => {
const collectionNames = await getAllCollectionNames(prisma);
for (const name of collectionNames) {
await deleteAllFromCollection(prisma, name);
}
};
_getLatestCompletedWorkout() async {
try {
QuerySnapshot workouts;
workouts = await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection('workouts')
.get();
for (var workout in workouts.docs) {
print('WORKOUT = ');
print(workout);
}
.....
What I really need is to get the last document saved; but before that I am just trying to fetch the "workouts" collection; the workouts.docs list always has 0 items. There are 2 items in the DB. What is wrong with this code? Also how to get the last saved item?
As mentioned by Frank :
You can refer Alex answer here :
The simplest way to achieve this is to add a date
property to each object in your collection, then simply query it
according to this new property descending and call limit(1) function.
This is the required query:
this.historyRef = afs.collection<History>('history', ref => ref.orderBy('date', 'desc').limit(1));
this.history = this.historyRef.snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data() as Hisotory;
const docId = a.payload.doc.id;
return { docId, ...data };
});
});
This has been found since cloud_firestore updates that prevent app that not regiter in App Check to take updates seriously "but it store it with lighter id color" which Protect your Cloud Firestore resources from abuse, such as billing fraud or phishing
Kindly check and ensure your app is registered inside App Check in Firebase console
I have a couple of writes that I want to be done together.
So I used a write batch.
Since the write batch requires a document reference, I have been creating the document before that write batch operation.
DocumentReference accountHistoryDoc = await queryResult.reference.collection('accountHistory').add({});
This led to many empty documents. Since I'm still testing and debugging the app, I assume because of an exception after the creation of the document.
How can I make sure that an empty document isn't created in the case of failure?
I'm thinking of changing this line
wb.set(
accountHistoryDoc, // Change this line
{
'account': newAccount,
'serverTimestamp': FieldValue.serverTimestamp(),
'type': 'hisab',
},
);
to
wb.set(
await subscriberDoc.collection('accountHistory').add({}),// new line
{
'account': newAccount,
'serverTimestamp': FieldValue.serverTimestamp(),
'type': 'hisab',
},
);
Is this my thinking correct?
Rest of code:
QuerySnapshot query = await FirebaseFirestore.instance
.collection(CurrentUser.getCurrentUser().uid)
.where('mobile', isEqualTo: mobile)
.get();
QueryDocumentSnapshot queryResult = query.docs.first;
DocumentReference subscriberDoc = queryResult.reference;
DocumentReference accountHistoryDoc = await queryResult.reference.collection('accountHistory').add({}); // < -- new empty document here
WriteBatch wb = FirebaseFirestore.instance.batch();
// update the total account
wb.update(
subscriberDoc,
{
'totalAccount': subscriber.totalAccount + newAccount,
},
);
// add new document in account history
wb.set(
accountHistoryDoc,
await subscriberDoc.collection('accountHistory').add({}),
{
'account': newAccount,
'serverTimestamp': FieldValue.serverTimestamp(),
'type': 'hisab',
},
);
If you call CollectionReference.doc() without an argument, it generates a new unique DocumentReference without already creating that document in the database. You can then use this DocumentReference to create the new document inside the batched write.
Also see the documentation for the FlutterFire doc() method.
So I have this kind-a-like schema at the moment
user:{ _id: string,
shifts:[_id:string],
name: ... ,
...
}
And now I want to delete a shift._id from all my users who have this.
I allready have an array of all the users their id's who have this shift._id.
I've tried this, with shift_id as the id of the shift i want to delete:
userIdArray.forEach(user_id => {
UserSchema.update({_id: user_id}, {$pull: {shifts: shift_id} });
});
and got the error:
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:535:11)
Can somebody explain me what I did wrong?
Edit:
So what i did was, i called a function named:
function deleteShiftIdInUsers(users, shift_id){
users.forEach(user_id => {
UserSchema.update({_id: user_id}, {$pull: {shifts: shift_id} });
});}
and called this function in my async (req, res, next) route.
Now i just execute this code within the async function instead doing it like code...;
deleteShiftIdInUsers(users, shift_id);
res.status(200).json(...);
still new to js, so what did i do wrong?
I think, You need to call the mongoose function with async/await, you should use updateMany with $in instead of looping for user_id
var ObjectId = require("mongoose").Types.ObjectId
async function deleteShiftIdInUsers(users, shift_id){
users = users.map(user_id => ObjectId(user_id))
return await UserSchema.updateMany({"_id": {"$in": users}}, {"$pull": {"shifts": ObjectId(shift_id)} });
}
when you are calling this function
await deleteShiftIdInUsers(users, shift_id);
res.status(200).json(...);
All mongodb transaction examples I have seen so far don't allow reading results back. For example (pseudo code):
begin_transaction
collection_one.update(...)
collection_two.update(...)
commit_transaction
The problem is, what if I want to update collection_two based on the result of updating collection_one?
For example?
begin_transaction
result = collection_one.update(...)
if (result.update_count() > 0)
{
collection_two.update(...)
}
commit_transaction
I have never seen an example like the above? It seems that when use transaction, I can't get the result back.
Another example,
begin_transaction
result = collection_children.find({name: 'xxx'})
collection_team.delete({name in result})
commit_transaction
Basically, I want to perform a find on a collection, and based the find result to perform a second action on a different collection.
And I want the 2 actions together be atomic.
Here is an example of how this works as expected with Mongoose. The same example obviously is possible without Mongoose.
var author = new Author({ email: "test22#test.com", other: [{a: 1}});
var book = new Book({ title: 'ABC' })
let doFoo = async () => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const opts = { session, new: true };
let _author = await author.save() // Notice we get back the record
let _book = await book.save()
// Notice we are using now the new id of the just saved record
await Author.findOneAndUpdate({ _id: _author.id }, { $set: { other: { foo: 2 } }}, opts);
await Book.findOneAndUpdate({ _id: _book.id }, { $set: { title: "ABC" }}, opts);
await session.commitTransaction();
session.endSession();
} catch (error) {
await session.abortTransaction();
session.endSession();
throw error; // Rethrow so calling function sees error
}
}
doFoo()
So in the example above we create/save two different records in their respective collections and after that based on the new records we go back and update them.