mongodb, express.js. Add new doc to array of documents selector is id - mongodb

I want to add a new document to an array of documents. So I pass in my param which is the _id of the document I want to add to. Then I need to just add it to the array. I thought I had it working but it was actually adding a nested array to that array. I realized this because I am also trying to sort it so newly added documents are at top. So I ended up having to go back and try and fix my add query. As of now it basically just says cannot add values. This is why I have been using mongodb client, express, await.
I have been looking at mongodb manual and trying what they have but cannot get it to work, obviously something wrong with my adding of new document. Anyone see the issue or show me an example? Thanks!
app.post("/addComment/:id", async (request, response) => {
let mongoClient = new MongoClient(URL, { useUnifiedTopology: true });
try {
await mongoClient.connect();
let id = new ObjectId(request.sanitize(request.params.id));
request.body.comments = { $push: {"comments.author": "myTestPOSTMAN - 1", "comments.comment":
"myTestCommPostMan - 1"}};
let selector = { "_id":id };
//let newValues = {$push: {"comments.comment": "myTestCommPostMan - 1", "comments.author":
"myTestPOSTMAN - 1"}};
let newValues = request.body.comments;
let result = await mongoClient.db(DB_NAME).collection("photos").updateOne(selector,
newValues);
if (JSON.parse(result).n <= 0) {
response.status(404);
response.send({error: "No documents found with ID"});
mongoClient.close();
return;
}
response.status(200);
response.send(result);
} catch (error) {
response.status(500);
response.send({error: error.message});
throw error;
} finally {
mongoClient.close();
}
});
Using post man this is what my json looks like and what the array of documents looks like I am trying to add to.
{"comments": [
{
"comment": "pm - test3",
"author": "pm - test4"
}
]
}

do the mongodb connection outside the function, no need to connect and disconnect everytime when function call, don't create unusual variables too much.
for push object you need to provide main key name and assign object to it.
let mongoClient = new MongoClient(URL, { useUnifiedTopology: true });
await mongoClient.connect();
app.post("/addComment/:id", async (request, response) => {
try {
let result = await mongoClient.db(DB_NAME).collection("photos").updateOne(
{ "_id": new ObjectId(request.sanitize(request.params.id)) },
{ $push: { comments: request.body.comments } }
);
if (JSON.parse(result).n <= 0) {
response.status(404).send({ error: "No documents found with ID" });
return;
}
response.status(200).send(result);
} catch (error) {
response.status(500).send({ error: error.message });
}
});

Related

Conditional Query based on Variable value in MongoDB

I am building a list view page with multiple filters using .net core as backend and mongodb as database. I want to filter data based on some condition such that if the variable is passed as blank, it should retrieve all data, otherwise only the matching data.
In mssql this can be achieved easily by
where (#SymbolCode = '' or SymbolCode = #SymbolCode)
What is the option in mongodb filter
var filter = new BsonDocument
{
{
"symbolCode", SymbolCodeSearchString // If string SymbolCodeSearchString is blank, it should retrieve all data else the search data
}
};
Sample Data
{
"_id": {
"$oid": "60ed91bc65675f966c0eec46"
},
"symbolCode": "F",
"timestamp": {
"$date": "2021-07-13T13:14:35.909Z"
}
}
you should check symboleCode before query on mongodb
first check if symbolCode is empty or ""
define filter query as
var filter = new BsonDocument
{
};
else if it was not empty
var filter = new BsonDocument
{
{
"symbolCode", SymbolCodeSearchString // If string SymbolCodeSearchString is blank, it should retrieve all data else the search data
}
};
I was able to figure out. Thanks to #Naimi and #Takis. Building a dynamic filter is the key. Posting here the answer so that it could help anyone
var filterQuery = new BsonDocument
{
{
"symbolCode", new BsonDocument
{
{ "$regex", SymbolCodeString},
{ "$options", "i"}
}
}
};
if (fromDate != "" && toDate != "")
{
BsonElement dateFilter = new("timestamp", new BsonDocument
{
{ "$gt", fromDate},
{ "$lt", toDate}
});
filterQuery.Add(dateFilter);
}
By this way, i am able to add any number of filters dynamically

Mongoose, change value in array

I am trying to update a value in an array, i tried doing it this way:
Mongoose, update values in array of objects
But it doesn't seem to work for me:
let myLessonDB = await myLessonSchema.findById(req.params.id);
myLessonDB.update(
{ 'lessons.lesson': req.body.lessonID },
{ $set: { 'lessons.$.present': true } }
);
myLessonDB return this:
{"_id":"5eafff00726616772ca852e2",
"lessons":[{"level":"1","present":false,"lesson":"5eb00211154ac86dc8459d6f"}],
"__v":0}
I am trying to change a value in lessons by the lesson ID as shown but it doesn't work.
No errors or anything it looks like it cant find the object in the array
Anyone know what I am doing wrong?
let myLessonDB = await myLessonSchema.findById(req.params.id);
myLessonDB.lessons.map(oneLes => {
if(oneLes.lesson == req.body.lessonID){
oneLes.present = true;
}
})
myLessonDB.save().then( finalLesson => {
console.log(finalLesson);
})

Mongodb will not create document within if/else statement

New to MongoDB. I'm trying to add a new document to an existing collection only if a document with the same name does not already exist within that collection. I'm using an if/else statement first to check whether there is a document with the same name as the new entry, and if not, an else statement that will create the new entry. Whatever I try, the new document is not getting added, and instead it returns an empty array. I'd be grateful for any help.
I've tried switching the if/else statements; checking for null and undefined values upon return of if statement
app.post('/cocktails/new', isLoggedIn, (req, res) => {
// add to DB then show item
let name = req.body.name;
let style = req.body.style;
let spirit = req.body.spirit;
let image = req.body.image;
let description = req.body.description;
let newCocktail = {name: name, style: style, base: spirit, image:
image, description: description}
Cocktail.find({name}, function (err, existCocktail) {
if(existCocktail){
console.log(existCocktail)
}
else {Cocktail.create(newCocktail, (err, cocktail) => {
console.log(cocktail)
if (err) {console.log(err)}
else {
res.redirect('/cocktails/' + cocktail._id)}
})
}
})
})
In the event document is not found with the if function, else statements will execute, leading to new document being created with the newCocktail object.
You should use findOne instead of find.
find returns an empty array when no docs found.
The following expression returns true, so your existCocktail condition becomes true, causing your new data not added.
[] ? true : false
Also I refactor your code a bit, you can use destructuring your req.body.
app.post("/cocktails/new", isLoggedIn, (req, res) => {
// add to DB then show item
const { name, style, spirit, image, description } = red.body;
let newCocktail = {
name,
style,
base: spirit,
image,
description
};
Cocktail.findOne({ name }, function(err, existCocktail) {
if (existCocktail) {
console.log(existCocktail);
res.status(400).json({ error: "Name already exists" });
} else {
Cocktail.create(newCocktail, (err, cocktail) => {
console.log(cocktail);
if (err) {
console.log(err);
res.status(500).json({ error: "Something went bad" });
} else {
res.redirect("/cocktails/" + cocktail._id);
}
});
}
});
});

how to read back result when using mongodb transaction?

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.

Is there a way to perform a "dry run" of an update operation?

I am in the process of changing the schema for one of my MongoDB collections. (I had been storing dates as strings, and now my application stores them as ISODates; I need to go back and change all of the old records to use ISODates as well.) I think I know how to do this using an update, but since this operation will affect tens of thousands of records I'm hesitant to issue an operation that I'm not 100% sure will work. Is there any way to do a "dry run" of an update that will show me, for a small number of records, the original record and how it would be changed?
Edit: I ended up using the approach of adding a new field to each record, and then (after verifying that the data was right) renaming that field to match the original. It looked like this:
db.events.find({timestamp: {$type: 2}})
.forEach( function (e) {
e.newTimestamp = new ISODate(e.timestamp);
db.events.save(e);
} )
db.events.update({},
{$rename: {'newTimestamp': 'timestamp'}},
{multi: true})
By the way, that method for converting the string times to ISODates was what ended up working. (I got the idea from this SO answer.)
My advice would be to add the ISODate as a new field. Once confirmed that all looks good you could then unset the the string date.
Create a test environment with your database structure. Copy a handful of records to it. Problem solved. Not the solution you were looking for, I'm sure. But, I believe, this is the exact circumstances that a 'test environment' should be used for.
Select ID of particular records that you would like to monitor. place in the update {_id:{$in:[<your monitored id>]}}
Another option which depends of the amount of overhead it will cause you -
You can consider writing a script, that performs the find operation, add printouts or run in debug while the save operation is commented out. Once you've gained confidence you can apply the save operation.
var changesLog = [];
var errorsLog = [];
events.find({timestamp: {$type: 2}}, function (err, events) {
if (err) {
debugger;
throw err;
} else {
for (var i = 0; i < events.length; i++) {
console.log('events' + i +"/"+(candidates.length-1));
var currentEvent = events[i];
var shouldUpdateCandidateData = false;
currentEvent.timestamp = new ISODate(currentEvent.timestamp);
var change = currentEvent._id;
changesLog.push(change);
// // ** Dry Run **
// currentEvent.save(function (err) {
// if (err) {
// debugger;
// errorsLog.push(currentEvent._id + ", " + currentEvent.timeStamp + ', ' + err);
// throw err;
// }
// });
}
console.log('Done');
console.log('Changes:');
console.log(changesLog);
console.log('Errors:');
console.log(errorsLog);
return;
}
});
db.collection.find({"_manager": { $exists: true, $ne: null }}).forEach(
function(doc){
doc['_managers']=[doc._manager]; // String --> List
delete doc['_manager']; // Remove "_managers" key-value pair
printjson(doc); // Debug by output the doc result
//db.teams.save(doc); // Save all the changes into doc data
}
)
In my case the collection contain _manager and I would like to change it to _managers list. I have tested it in my local working as expected.
In the several latest versions of MongoDB (at least starting with 4.2), you could do that using a transaction.
const { MongoClient } = require('mongodb')
async function main({ dryRun }) {
const client = new MongoClient('mongodb://127.0.0.1:27017', {
maxPoolSize: 1
})
const pool = await client.connect()
const db = pool.db('someDB')
const session = pool.startSession()
session.startTransaction()
try {
const filter = { id: 'some-id' }
const update = { $rename: { 'newTimestamp': 'timestamp' } }
// This is the important bit
const options = { session: session }
await db.collection('someCollection').updateMany(
filter,
update,
options // using session
)
const afterUpdate = db.collection('someCollection')
.find(
filter,
options // using session
)
.toArray()
console.debug('updated documents', afterUpdate)
if (dryRun) {
// This will roll back any changes made within the session
await session.abortTransaction()
} else {
await session.commitTransaction()
}
} finally {
await session.endSession()
await pool.close()
}
}
const _ = main({ dryRun: true })