I have a Nest.JS and TypeORM code that deletes multiple rows and then inserts multiple rows.
async bulkUpdate(
where: any = {},
something: SomethingDto[],
connection: Connection,
) {
const queryRunner = connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// delete all
await queryRunner.manager.remove(something,{chunk: 1000});
// create new ones
await queryRunner.manager.save(something, { chunk: 1000 });
return await queryRunner.commitTransaction();
} catch (error) {
if (queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction();
return Promise.reject(error);
}
}
}
I cannot figure out how to remove all records from the table for concrete userId.
Related
What I want: When the application is first opened, I add the data I receive from the service to the list. I want to save the data I added to the list to my sqflite database with a for loop.
Problem: Data (266 units) is added to the list from the service without any problems. The first 50 data are added to the database without any problems. After 50 data, the application does not take any action.
My codes were working fine before, I didn't understand what was happening, so it started giving errors.
The method I call on the splash screen:
_getData() async {
await Provider.of<FirestoreDataNotifier>(context, listen: false)
.fetchRecommendedContentsOfTheWeek();
Config.sharedPreferences = await SharedPreferences.getInstance();
}
Notifier method:
Future<void> fetchRecommendedContentsOfTheWeek() async {
List<RecommendedVideoModel> _tempList = [];
_isCompletely = false;
try {
Some transactions...
await getAndAddData();
notifyListeners();
} catch (e) {
e.toString();
}
}
print('-> getAndAddData Successfully!'); It doesn't continue the line, it stays in the loop.
Future<void> getAndAddData() async {
List<ContentModel> _list = [];
_list = await FirestoreService().getAllData();
for (var element in _list) {
await AllContentDatabaseHelper.instance.add(
ContentModel(
title: element.title,
thumbnailUrl: element.thumbnailUrl,
content: element.content,
id: element.id,
categoryName: element.categoryName,
categoryId: element.categoryId,
estimatedTime: element.estimatedTime,
publishedDate: element.publishedDate,
videoUrl: element.videoUrl!.isEmpty ? ' ' : element.videoUrl),
);
}
print('-> getAndAddData Successfully!');
_list.clear();
await getDataSize();
_isCompletely = true;
notifyListeners();
}
And add method:
Future<void> add(ContentModel contentModel) async {
Database db = await instance.database;
await db.insert(_tableName, contentModel.toMap());
print('-> OK!');
}
Try using a batch,
batch = db.batch();
batch.insert(_tableName, contentModel.toMap());
results = await batch.commit(noResult: true);
From this : https://pub.dev/packages/sqflite
UPDATE:
Since, first answer's not working, try the raw approach, something like this:
insertData(List< ContentModel> rows) async {
final db = await database;
var buffer = new StringBuffer();
rows.forEach((c) {
if (buffer.isNotEmpty) {
buffer.write(",\n");
}
buffer.write("(");
buffer.write(c.id);
buffer.write(", '");
buffer.write(c.title);
buffer.write("', '");
buffer.write(c.thumbnailUrl);
buffer.write("', '");
buffer.write(c.content);
buffer.write("', '");
buffer.write(c.categoryName);
buffer.write("', ");
buffer.write(c.categoryId);
buffer.write(", '");
buffer.write(c.estimatedTime);
buffer.write("', '");
buffer.write(c.publishedDate);
buffer.write("', '");
buffer.write(c.videoUrl);
buffer.write("'),");
});
var raw =
await db.rawInsert("INSERT Into $_tableName (id,title, thumbnailUrl, content, categoryName, categoryId, estimatedTime, publishedDate,videoUrl)"
" VALUES ${buffer.toString()}");
return raw;
}
To get something like this:
INSERT INTO 'tablename' ('column1', 'column2', 'column3') VALUES
('data1', 'data2', 'data3'),
('data1', 'data2', 'data3'),
('data1', 'data2', 'data3');
Im calling a function to get data from Excel file and upload it to my Firestore as following
floatingActionButton: FloatingActionButton(onPressed: () async {
Utils.showLoading(context);
await FireStoreServices.bulkUploadFromExcelToFireStore(
collectionName: 'test',
fileName: 'test',
sheetName: 'test');
Navigator.pop(context);
}),
the problem is my Progress loading indicator not working as expected in this case (not spinning only shows and freeze until the function complete after that its popped)
i tried to replace the awaited function 'bulkUploadFromExcelToFireStore' with Future.delayed and it worked as expected
await Future.delayed(const Duration(seconds: 3), () {});
what might be the problem ?
here is the code of bulkUploadFromExcelToFireStore function
static Future bulkUploadFromExcelToFireStore(
{required String fileName,
required String sheetName,
required String collectionName}) async {
try {
final rowsData = await Utils.readExcelFileData(
excelFilePath: fileName, sheetName: sheetName);
rowsData.removeAt(0);
for (var row in rowsData) {
firebaseFirestore.collection(collectionName).doc(row[0]).set(data, SetOptions(merge: true));
}
} catch (e) {
print('Cached ERROR MESSAGE = = = = ${e.toString()}');
}
I added some validations inside your function to check for possible failures.
It would also be interesting to validate a failure warning and terminate the Progression Indication initialization.
static Future<String> bulkUploadFromExcelToFireStore({required String fileName, required String sheetName,required String collectionName}) async {
try {
final rowsData = await Utils.readExcelFileData(excelFilePath: fileName, sheetName: sheetName);
rowsData.removeAt(0);
if(rowsData.length == 0) {
return "No Items!";
} else {
for (var row in rowsData) {
firebaseFirestore?.collection(collectionName)?.doc(row[0])?.set(data, SetOptions(merge: true));
}
return "Item allocated!";
}
} catch (e) {
return e.toString();
}
}
I really need your help. My MongoDB transaction with #NestJs/mongoose not working...When My stripe payment fails rollback is not working... Still, my order collection saved the data...How can I fix this issue..?
async create(orderData: CreateOrderServiceDto): Promise<any> {
const session = await this.connection.startSession();
session.startTransaction();
try {
const createOrder = new this.orderModel(orderData);
const order = await createOrder.save();
await this.stripeService.charge(
orderData.amount,
orderData.paymentMethodId,
orderData.stripeCustomerId,
);
await session.commitTransaction();
return order;
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
await session.endSession();
}
}
I had the same issue and i found that on github: Mongo DB Transactions With Mongoose & Nestjs
So I think, according this issue, you have to call the create method of your model, like that:
const order = await this.orderModel.create(orderData, { session });
as you can see, the Model.create method has an overload with SaveOptions as parameter:
create(docs: (AnyKeys<T> | AnyObject)[], options?: SaveOptions): Promise<HydratedDocument<T, TMethodsAndOverrides, TVirtuals>[]>;
it takes an optional SaveOptions parameter that can contain the session:
interface SaveOptions {
checkKeys?: boolean;
j?: boolean;
safe?: boolean | WriteConcern;
session?: ClientSession | null;
timestamps?: boolean;
validateBeforeSave?: boolean;
validateModifiedOnly?: boolean;
w?: number | string;
wtimeout?: number;
}
Please note that Model.save() can also take a SaveOptions parameter.
So you can also do as you did like that:
const createOrder = new this.orderModel(orderData);
const order = await createOrder.save({ session });
A little further...
As i do many things that require a transaction, I came up with this helper to avoid many code duplication:
import { InternalServerErrorException } from "#nestjs/common"
import { Connection, ClientSession } from "mongoose"
export const mongooseTransactionHandler = async <T = any>(
method: (session: ClientSession) => Promise<T>,
onError: (error: any) => any,
connection: Connection, session?: ClientSession
): Promise<T> => {
const isSessionFurnished = session === undefined ? false : true
if (isSessionFurnished === false) {
session = await connection.startSession()
session.startTransaction()
}
let error
let result: T
try {
result = await method(session)
if (isSessionFurnished === false) {
await session.commitTransaction()
}
} catch (err) {
error = err
if (isSessionFurnished === false) {
await session.abortTransaction()
}
} finally {
if (isSessionFurnished === false) {
await session.endSession()
}
if (error) {
onError(error)
}
return result
}
}
Details
the optional parameter session is in case you are doing nested nested transaction.
that's why i check if the session is provided. If it is, it means we are in a nested transaction. So we'll let the main transaction commit, abort and end the session.
Example
for example: you delete a User model, and then the user's avatar which is a File model.
/** UserService **/
async deleteById(id: string): Promise<void> {
const transactionHandlerMethod = async (session: ClientSession): Promise<void> => {
const user = await this.userModel.findOneAndDelete(id, { session })
await this.fileService.deleteById(user.avatar._id.toString(), session)
}
const onError = (error: any) => {
throw error
}
await mongooseTransactionHandler<void>(
transactionHandlerMethod,
onError,
this.connection
)
}
/** FileService **/
async deleteById(id: string, session?: ClientSession): Promise<void> {
const transactionHandlerMethod = async (session: ClientSession): Promise<void> => {
await this.fileModel.findOneAndRemove(id, { session })
}
const onError = (error: any) => {
throw error
}
await mongooseTransactionHandler<void>(
transactionHandlerMethod,
onError,
this.connection,
session
)
}
So, in short:
You can use it like this:
async create(orderData: CreateOrderServiceDto): Promise<any> {
const transactionHandlerMethod = async (session: ClientSession): Promise<Order> => {
const createOrder = new this.orderModel(orderData);
const order = await createOrder.save({ session });
await this.stripeService.charge(
orderData.amount,
orderData.paymentMethodId,
orderData.stripeCustomerId,
);
return order
}
const onError = (error: any): void => {
throw error
}
const order = await mongooseTransactionHandler<Order>(
transactionHandlerMethod,
onError,
this.connection
)
return order
}
Hope it'll help.
EDIT
Do not abuse of the model.save({ session }) of the same model in nested transcations.
For some reasons it will throw an error the model is updated too many times.
To avoid that, prefer using model embeded methods that update and return a new instance of your model (model.findOneAndUpdate for example).
Been trying to copy subcollections of a collection into another collection. The code below is aimed at that, but jumps from the first then and logs out "Done" without logging out anything before.
So the question is what is not correct here?
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
try {
await db.collection("users").get().then((query) => {
return query.forEach(async (doc) => {
console.log("Here"); //This doesn't print
const polCollection = await db.collection("users").doc(doc.id).collection("xyz").get();
if (polCollection.docs.length > 0) { //This checks if any subcollections
for (const x of polCollection.docs) { //This copies them into a doc in the copy collection
db.collection("CopyUsers")
.doc(doc.id)
.set({ x : x.data() }, { merge: true });
}
}
});
})
.then(() => {
console.log("Done"); //This is the only thing that prints in the console
res.end();
})
.catch((e) => {
console.log("e", e);
res.end();
});
} catch (error) {
console.log("error", error);
res.end();
}
});
After the suggestion below, it now looks as follows:
exports = module.exports = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
const promises = [];
let count = 0;
let size = 0;
return await admin
.firestore()
.collection("testUsers")
.get()
.then((query) => {
console.log("query length:", query.size); //prints x of users
size = query.size;
query.forEach(async (doc) => {
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
await admin
.firestore()
.collection("testUsers")
.doc(doc.id)
.collection("xyz")
.get()
.then(async (polCollection) => {
if (polCollection.docs.length > 0) {
for (const x of polCollection.docs) {
return await admin
.firestore()
.collection("testBackUpUsers")
.doc(doc.id)
.set(
{ xyz: x.data() },
{ merge: true }
);
}
} else {
return;
}
})
.catch((e) => console.log("error from then after get xyz", e));
};
count++;
return promises.push(promise);
});
return promises;
})
.then(async (promises) => {
if (size <= count) {
console.log("running return Promise.all(promises)", promises.length); //prints number of promises = users
return Promise.all(promises);
}
})
.catch((e) => console.log("err from the last catch", e));
});
Any thoughts?
Unfortunately the forEach iterator does not support async/await. Even if you write an await inside it will just go trough it without waiting on the execution.
I would recommend to use Promise.all. That would also execute the code in parallel and would finish faster.
If you would only change data you could also use a batch change but in your example you first get the data and then change it.
Here is an example how you could write your code:
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
const promises = [];
try {
const query = await db.collection("users").get();
query.forEach((doc) => {
console.log("doc", doc);
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
const polCollection = await db
.collection("users")
.doc(doc.id)
.collection("xyz")
.get();
if (polCollection.docs.length > 0) {
//This checks if any subcollections
for (const x of polCollection.docs) {
//This copies them into a doc in the copy collection
await db
.collection("CopyUsers")
.doc(doc.id)
.set({ x: x.data() }, { merge: true });
}
}
};
promises.push(promise);
});
console.log("promises", promises);
await Promise.all(promises);
console.log("Done");
res.end();
} catch (error) {
console.log("error", error);
res.end();
}
});
I have a post schema and user schema and I want to merge them together. I waant to know how to do it. So far I have this code but I keep returning promises. When I add then after the .map, I get no result. Any help would be appreciated
let posts = await Post.find();
console.log(posts);
let test = await posts.map(async(post) => {
const creator = await User.findOne({token: post.creator});
var username = null;
if(creator){
username = creator.username;
}
return {...post._doc, username: username};
});
return posts;
you can use q and async module
const q = require("q");
const async = require("async");
async function mainFunction() {
try {
let posts = await Post.find();
let result = await getUser(posts);
console.log(result)
} catch (error) {
console.error(error);
return { error: error };
}
}
async function getUser (posts) {
let defer = q.defer;
let test = [];
async.eachSeries(posts,async (post) => {// like loop
try {
let creator = await User.findOne({ token: post.creator });
var username = null;
if (creator) {
username = creator.username;
}
test.push({ ...post._doc, username: username });
} catch (error) {
console.log(error);
}
},() => {//callback
console.log("finish loop");
defer.resolve(test); // when finished loop return result
}
);
return defer.promise;
};