mongo forEach unable to update array in document - mongodb

I'm trying write a mongo script to run in RoboMongo that will loop through all documents in a collection. Each document contains an array myArray. The documents look like this:
{
"name": "myApp",
"myArray": [
{ "env": "dev", "dbHost": "db2dev.local" },
{ "env": "prod", "dbHost": "db1prod.local" }
]
I want to copy the dbHost field that is defined in dev to prod. So the above result would be:
{
"name": "myApp",
"myArray": [
{ "env": "dev", "dbHost": "db2dev.local" },
{ "env": "prod", "dbHost": "db2dev.local" }
]
When I try to access the field myArray[0] I get a syntax error that says:
TypeError: myDoc.myArray[0] is undefined
The function is something like this:
db.myCollection.find().forEach( function(myDoc) {
var devIdx = 0;
var prodIdx = 1;
if (myDoc.myArray[0].env !== 'dev')}
devIdx = 1;
prodIdx = 0;
}
myDoc.myArray[prodIdx].dbHost = myDoc.myArray[devIdx].dbHost;
print(myDoc);
});
I've examined the collection (it is very small) and each document has a myArray field as it should with exactly two values (one for dev and one for prod) in the array.
What am I doing wrong? What is the correct syntax to use inside a mongo script? Is updating arrays in a document not supported?
Searching for solution
I've searched and found forEach examples but most are trivial and none include an array being accessed or changed.
The mongo docs are also very simplistic: https://docs.mongodb.com/v3.6/reference/method/cursor.forEach/

Mongo javascript does not allow you to access arrays directly like you are trying to do (unless you are in a for loop). So a solution is shown below:
db.myCollection.find({}).forEach( function(myDoc) {
var foundDevEntry = null;
var updatedProdEntry = false;
// First time loop to get a copy of the dev entry
for (var idx in myDoc.myArray) {
if (myDoc.myArray[idx].env === 'dev') {
foundDevEntry = myDoc.myArray[idx];
}
}
// 2nd time loop to update the value
for (var idx in myDoc.myArray) {
if (myDoc.myArray[idx].env === 'prod') {
myDoc.myArray[idx].dbHost = foundDevEntry.dbHost;
}
}
// Now update the database with this change
db.myCollection.update({_id: myDoc._id}, {$set: {"myArray": myDoc.myArray}});
print(myDoc); // So results are also returned when query is run
});
I've stripped out error checking to focus on the change required. What (to me) is odd is that the syntax myDoc.myArray[idx] is actually valid but only inside a loop!
The following references helped me come to a solution:
Update in forEach on mongodb shell
https://www.mysoftkey.com/mongodb/how-to-use-foreach-loop-in-mongodb-to-manipulate-document/
I should add that some solutions I read said that to update an array you had to re-build the array (https://stackoverflow.com/a/22657219/3281336). I did not do that in my solution and it did work but wanted to share it.

Related

How to make mongo properly run my javascript script

I'm trying to update my document named "things" running javascript script from mongo shell. When I explicitly say which field to update it works like a charm but I'm having problem when I'm trying to read field name from the array.
This is my code:
var continuousWithMissingValues = ["A2","A14"];
var categoricWithMissingValues = ["A1", "A4", "A5", "A6", "A7"];
for (var i = 0; i < continuousWithMissingValues.length; i++){
// db.things.update({A2: "?" },{$set: {A2: -1 }}, { multi : true }); this line is working properly
db.things.update({continuousWithMissingValues[i]: "?" },{$set: {continuousWithMissingValues[i]: -1 }}); //if I try to update while reading values from the array I'm getting error
print('Updated missing values for'+ continuousWithMissingValues[i]);
}
I have also tried using this line without any luck(this does not produce error but it doesn't update anything):
db.things.update({"continuousWithMissingValues.i": "?" },{$set: {"continuousWithMissingValues.i": -1 }});
The mongo shell doesn't support computed property names, so you have to build up your query and update objects in a few steps:
var query = {};
query[continuousWithMissingValues[i]] = "?";
var update = {$set: {}};
update.$set[continuousWithMissingValues[i]] = -1;
db.things.update(query, update);

Invoke db.eval in FindAndModify using MongoDB C# Client

I have the following Document:
{
"_id": 100,
"Version": 1,
"Data": "Hello"
}
I have a function which return a number from a sequence:
function getNextSequence(name) {
var ret = db.Counter.findAndModify(
{
query: { _id: name },
update: { $inc: { value: 1 } },
new: true,
upsert: true
}
);
return ret.value;
}
I can use this for optimistic concurrency by performing the following Mongo command:
db.CollectionName.findAndModify({
query: { "_id" : NumberLong(100), "Version" : 1 },
update: { "$set" : {
"Data": "Here is new data!",
"Version" : db.eval('getNextSequence("CollectionName")') }
},
new: true
}
);
This will update the document (as the _id and Version) match, with the new Data field, and also the new number out of the eval call.
It also returns a modified document, from which I can retrieve the new Version value if I want to make another update later (in the same 'session').
My problem is:
You cannot create an Update document using the MongoDB C# client that will serialize to this command.
I used:
var update = Update.Combine(
new UpdateDocument("$set", doc),
Update.Set(versionMap.ElementName, new BsonJavaScript("db.eval('getNextSequence(\"Version:CollectionName\")')")))
);
If you use what I first expected to perform this task, BsonJavascript, you get the following document, which incorrectly sets Version to a string of javascript.
update: { "$set" : {
"Data": "Here is new data!",
"Version" : { "$code" : "db.eval('getNextSequence(\"Version:CollectionName\")')" }
}
}
How can I get MongoDB C# client to serialize an Update document with my db.eval function call in it?
I have tried to add a new BsonValue type in my assembly which I would serialize down to db.eval(''); However there is a BsonType enum which I cannot modify, without making a mod to MongoDB which I would not like to do incase of any issues with the change, compatibility etc.
I have also tried simply creating the Update document myself as a BsonDocument, however FindAndModify will only accept an IMongoUpdate interface which a simply a marker that at present I find superfluous.
I have just tried to construct the command manually by creating a BsonDocument myself to set the Value: db.eval, however I get the following exception:
A String value cannot be written to the root level of a BSON document.
I see no other way now than drop down to the Mongo stream level to accomplish this.
So I gave up with trying to get Mongo C# Client to do what I needed and instead wrote the following MongoDB function to do this for me:
db.system.js.save(
{
_id : "optimisticFindAndModify" ,
value : function optimisticFindAndModify(collectionName, operationArgs) {
var collection = db.getCollection(collectionName);
var ret = collection.findAndModify(operationArgs);
return ret;
}
}
);
This will get the collection to operate over, and execute the passed operationArgs in a FindAndModify operation.
Because I could not get the shell to set a literal value (ie, not a "quoted string") on a javascript object, I had to to this in my C# code:
var counterName = "Version:" + CollectionName;
var sequenceJs = string.Format("getNextSequence(\"{0}\")", counterName);
var doc = entity.ToBsonDocument();
doc.Remove("_id");
doc.Remove(versionMap.ElementName);
doc.Add(versionMap.ElementName, "SEQUENCEJS");
var findAndModifyDocument = new BsonDocument
{
{"query", query.ToBsonDocument()},
{"update", doc},
{"new", true},
{"fields", Fields.Include(versionMap.ElementName).ToBsonDocument() }
};
// We have to strip the quotes from getNextSequence.
var findAndModifyArgs = findAndModifyDocument.ToString();
findAndModifyArgs = findAndModifyArgs.Replace("\"SEQUENCEJS\"", sequenceJs);
var evalCommand = string.Format("db.eval('optimisticFindAndModify(\"{0}\", {1})');", CollectionName, findAndModifyArgs);
var modifiedDocument = Database.Eval(new EvalArgs
{
Code = new BsonJavaScript(evalCommand)
});
The result of this is that I can now call my Sequence Javascript, the getNextSequence function, inside the optimisticFindAndModify function.
Unforunately I had to use a string replace in C# as again there is no way of setting a BsonDocument to use the literal type db.eval necessary, although Mongo Shell likes it just fine.
All is now working.
EDIT:
Although, if you really want to push boundaries, and are actually awake, you will realize this same action can be accomplished by performing an $inc on the Version field.... and none of this is necessary....
However: If you want to follow along to the MongoDB tutorial on how they to say to implement concurrency, or you just want to use a function in a FindAndModify, this will help you. I know I'll probably refer back to it a few times in this project!

How to append a key:value to a MongoDB cursor?

Is it possible to append a key:value to a MongoDB cursor?
I tried this:
cursor = collection.find(query,projection)
cursor['my_message'] = "value here" # trying to add key:value here
But it doesn't seem to work (500).
In more context, this works:
dbname = 'my_db'
db = connection[dbname]
collection = db.my_collection
query = {'key_1': my_var}
projection = {'key_2':1}
cursor = collection.find(query,projection)
response.content_type = 'application/json'
return dumps(cursor)
This doesn't:
dbname = 'my_db'
db = connection[dbname]
collection = db.my_collection
query = {'key_1': my_var}
projection = {'key_2':1}
cursor = collection.find(query,projection)
cursor['my_message'] = "value here" # trying to add key:value here
response.content_type = 'application/json'
return dumps(cursor)
Edit: And just to visualise what is being returned successfully (without the appended value), it is something like:
[{ document_1 },{ document_2 },{ document_3 }]
And I expect it to look something like:
["my_message":"value here",{ document_1 },{ document_2 },{ document_3 }]
Edit: I tried the following as an alternative and also got a 500.
entries = []
cursor = collection.find(query,projection)
for entry in cursor:
entries.append(entry)
entries['my_message'] = "value here"
response.content_type = 'application/json'
return dumps(entries)
Really, WiredPrarie answered this for you right at the beginning, and everyone is saying the same thing.
We know what you want to do. You want your serialized response to be sent back with some information you want to put in and then the resultset. I also presume that you want to use these results and your other data, likely loaded into some JavaScript processing store.
I have never seen anything that didn't expect some sort of structure like:
{
"result": "ok",
"meta": [{ akey: "avalue"}, {bkey: "bvalue"}],
"results:[ // this is your 'entries' value here
{ document_1 },
{ document_2 },
{ document_3 },
....
So what everyone is saying is embed your entries into another structure that you are going to serialize and return. By trying to push your other keys into the entries list you are doing it the wrong way around.

Modifying an example( todos)

to attempt to get a solid understanding of meteor and to couple it with mongoDB I'd like to ask one simple question.
The below is supplied with the example, used to fill the DB with information if it's empty.
All I'm trying to do is add a line of code at the start that does something like Lists.remove(); at the begining. Simply so I can keep fooling around with the example, with the ability to restart meteor, resetting the information in the database. I'm just unsure what exactly is the data set. I know with one simple command like the one I listed above I can do this. I'm just looking at mongodb and meteor for the first time today, so thanks for being forgiving :)
// if the database is empty on server start, create some sample data.
Meteor.startup(function () {
if (Lists.find().count() === 0) {
var data = [
{name: "Meteor Principles",
contents: [
["Data on the Wire", "Simplicity", "Better UX", "Fun"],
["One Language", "Simplicity", "Fun"],
["Database Everywhere", "Simplicity"],
["Latency Compensation", "Better UX"],
["Full Stack Reactivity", "Better UX", "Fun"],
["Embrace the Ecosystem", "Fun"],
["Simplicity Equals Productivity", "Simplicity", "Fun"]
]
},
{name: "Languages",
contents: [
["Lisp", "GC"],
["C", "Linked"],
["C++", "Objects", "Linked"],
["Python", "GC", "Objects"],
["Ruby", "GC", "Objects"],
["JavaScript", "GC", "Objects"],
["Scala", "GC", "Objects"],
["Erlang", "GC"],
["6502 Assembly", "Linked"]
]
},
{name: "Favorite Scientists",
contents: [
["Ada Lovelace", "Computer Science"],
["Grace Hopper", "Computer Science"],
["Marie Curie", "Physics", "Chemistry"],
["Carl Friedrich Gauss", "Math", "Physics"],
["Nikola Tesla", "Physics"],
["Claude Shannon", "Math", "Computer Science"]
]
}
];
var timestamp = (new Date()).getTime();
for (var i = 0; i < data.length; i++) {
var list_id = Lists.insert({name: data[i].name});
for (var j = 0; j < data[i].contents.length; j++) {
var info = data[i].contents[j];
Todos.insert({list_id: list_id,
text: info[0],
timestamp: timestamp,
tags: info.slice(1)});
timestamp += 1; // ensure unique timestamp.
}
}}});
Lists.remove() doesn't work because you have to specify something inside it, even nothing. Lists.remove({}) is what you want.
As explained in the docs http://docs.meteor.com/#remove:
As a safety measure, if selector is omitted (or is undefined), no documents will be removed. Set selector to {} if you really want to remove all documents from your collection.
So:
Meteor.startup(function () {
Lists.remove({});
if (Lists.find().count() === 0) {
console.log("Lists was empty");
...
Note: you will see the console.log in the terminal window you launched meteor from, not your browser console.
You could use meteor reset on the command line which erases your mongodb data

Nested document insert into MongoDB with C#

I am trying to insert nested documents in to a MongoDB using C#. I have a collection called categories. In that collection there must exist documents with 2 array, one named categories and one named standards. Inside those arrays must exist new documents with their own ID's that also contain arrays of the same names listed above. Below is what I have so far but I am unsure how to proceed. If you look at the code what I want to do is add the "namingConventions" document nested under the categories array in the categories document however namingConventions must have a unique ID also.
At this point I am not sure I have done any of this the best way possible so I am open to any and all advice on this entire thing.
namespace ClassLibrary1
{
using MongoDB.Bson;
using MongoDB.Driver;
public class Class1
{
public void test()
{
string connectionString = "mongodb://localhost";
MongoServer server = MongoServer.Create(connectionString);
MongoDatabase standards = server.GetDatabase("Standards");
MongoCollection<BsonDocument> categories = standards.GetCollection<BsonDocument>("catagories");
BsonDocument[] batch = {
new BsonDocument { { "categories", new BsonArray {} },
{ "standards", new BsonArray { } } },
new BsonDocument { { "catagories", new BsonArray { } },
{ "standards", new BsonArray { } } },
};
categories.InsertBatch(batch);
((BsonArray)batch[0]["categories"]).Add(batch[1]);
categories.Save(batch[0]);
}
}
}
For clarity this is what I need:
What I am doing is building a coding standards site. The company wants all the standards stored in MongoDB in a tree. Everything must have a unique ID so that on top of being queried as a tree it can be queried by itself also. An example could be:
/* 0 */
{
"_id" : ObjectId("4fb39795b74861183c713807"),
"catagories" : [],
"standards" : []
}
/* 1 */
{
"_id" : ObjectId("4fb39795b74861183c713806"),
"categories" : [{
"_id" : ObjectId("4fb39795b74861183c713807"),
"catagories" : [],
"standards" : []
}],
"standards" : []
}
Now I have written code to make this happen but the issue seems to be that when I add object "0" to the categories array in object "1" it is not making a reference but instead copying it. This will not due because if changes are made they will be made to the original object "0" so they will not be pushed to the copy being made in the categories array, at least that is what is happening to me. I hope this clears up what I am looking for.
So, based on your latest comment, it seems as though this is the actual structure you are looking for:
{
_id: ObjectId(),
name: "NamingConventions",
categories: [
{
id: ObjectId(),
name: "Namespaces",
standards: [
{
id: ObjectId(),
name: "TitleCased",
description: "Namespaces must be Title Cased."
},
{
id: ObjectId().
name: "NoAbbreviations",
description: "Namespaces must not use abbreviations."
}
]
},
{
id: ObjectId(),
name: "Variables",
standards: [
{
id: ObjectId(),
name: "CamelCased",
description: "variables must be camel cased."
}
]
}
]
}
Assuming this is correct, then the below is how you would insert one of these:
var collection = db.GetCollection("some collection name");
var root = new BsonDocument();
root.Add("name", "NamingConventions");
var rootCategories = new BsonArray();
rootCategories.Add(new BsonDocument
{
{ "id": ObjectId.GenerateNewId() },
{ "name", "Namespaces" },
{ "standards", new BsonArray() }
});
root.Add("categories", rootCategories);
//etc...
collection.Save(root);
Hope that helps, if not, I give up :).
So, I guess I'm confused by what you are asking. If you just want to store the namingConventions documents inside the array, you don't need a collection for them. Instead, just add them to the bson array and store them.
var categoriesCollection = db.GetCollection<BsonDocument>("categories");
var category = new BsonDocument();
var namingConventions = new BsonArray();
namingConventions.Add(new BsonDocument("convention1", "value"));
category.Add("naming_conventions", namingConventions);
categoriesCollection.Insert(category);
This will create a new document for a category, create an array in it called naming_conventions with a single document in it with an element called "convention1" and a value of "value".
I also am not quite sure what you are trying to accomplish. Perhaps if you posted some sample documents in JSON format we could show you the C# code to write documents that match that.
Alternatively, if you wish to discuss your schema, that could also be better done in the context of JSON rather than C#, and once a schema has been settled on then we can discuss how to write documents to that schema in C#.
One thing that didn't sound right in your original description was the statement "in that collection must exist 2 arrays". A collection can only contain documents, not arrays. The documents themselves can contain arrays if you want.
var filter = Builders<CollectionDefination>.Filter.Where(r => r._id== id);
var data = Builders<CollectionDefination>.Update.Push(f=>
f.categories,categoriesObject);
await _dbService.collection.UpdateOneAsync( filter,data);
Note: Make sure embedded document type [Categories] is array.