Using MongoDB and the latest 10gen C# driver (CSharpDriver-1.3.1.4349), I am trying to do an "in place" update and get back the # of documents effected in the result.
public static long SaveListings(string state, bool isActive, DateTime updateDate)
{
var result = Collection().Update(
Query.And(
Query.EQ("State", state),
Query.And(
Query.EQ("IsActive", isActive),
Query.LT("UpdateDate", updateDate))),
Update.Set("IsActive", false), UpdateFlags.Multi);
return result != null ? result.DocumentsAffected : -1;
}
The result is null for some reason. If I were doing this from the console, I could get the number of rows effected by doing this:
db.Listing.update( { State: state.Abbreviation, IsActive: true, UpdateDate: { $lt: expiredDate } }, { $set: { IsActive: false } }, false, true);
var numRows = db.getLastErrorObj().n;
Any idea what I'm doing wrong or is this a bug in the C# driver?
Update contains overloaded method that take SafeMode. Just add it to your code as fourth parameter to your update and should be not null:
...
UpdateFlags.Multi,
SafeMode.True);
That's not driver bug, it work as expected. Mongodb does not wait for document if get inserted without safe mode (therefore driver return null), but if you saying SafeMode = true -- you force mongodb wait until document get inserted.
Doing inserts and updates without specifying safe mode yields null because they're asynchronous operations - there's simply no way to know how the insert or update went.
Therefore, add e.g. SafeMode.True as the last argument in inserts and updates in cases where you care about the result. This will make the driver issue a getLastError command, which blocks until the insert/updated has completed:
var result = collection.Update(query, update, SafeMode.True);
You can read some more about getLastError and different safe modes here (general) and here (SafeMode with the C# driver).
Try getLastError here: http://www.webtropy.com/articles/art9-1.asp?f=GetLastError
Related
I'm trying to check that an update command accomplished but when I check for the nModified I'm getting 0 although I do see that the field value changes from one value to another (not kept the same value).
static async updateProfile(username, profileData) {
const usersCollection = db.dbConnection.collection(dbConfig.collectionNames.users);
const updateRes = await usersCollection.update({email: username},
{"$set": {
firstName: profileData.firstName,
lastName: profileData.lastName,
payment: profileData.payment,
}
});
return updateRes.result.nModified > 0;
}
Is there another way to verify the update?
One of the way by findAndModify method:
You can easily compare whole new object and verify each key.
db.getCollection('usertests').findAndModify({
query: {"email":"xxx#xxx.com"},
update: {name: "HHH", "email":"xxx#xxx.com"},
new: true
})
new: true is responsible to return whole updated document. If fail to update it will return null.
Take care here to pass the whole document while update.
update() only return a number of the documents that were successfully updated. So, your logic to check if updated successfully or not is also valid.
I'm trying to do something seemingly simple, update a views counter in MongoDB every time the value is fetched.
For example I've tried it with this method.
Meteor.methods({
'messages.get'(messageId) {
check(messageId, String);
if (Meteor.isServer) {
var message = Messages.findOne(
{_id: messageId}
);
var views = message.views;
// Increment views value
Messages.update(
messageId,
{ $set: { views: views++ }}
);
}
return Messages.findOne(
{_id: messageId}
);
},
});
But I can't get it to work the way I intend. For example the if(Meteor.isServer) code is useless because it's not actually executed on the server.
Also the value doesn't seem to be available after findOne is called, so it's likely async but findOne has no callback feature.
I don't want clients to control this part, which is why I'm trying to do it server side, but it needs to execute everytime the client fetches the value. Which sounds hard since the client has subscribed to the data already.
Edit: This is the updated method after reading the answers here.
'messages.get'(messageId) {
check(messageId, String);
Messages.update(
messageId,
{ $inc: { views: 1 }}
);
return Messages.findOne(
{_id: messageId}
);
},
For example the if(Meteor.isServer) code is useless because it's not
actually executed on the server.
Meteor methods are always executed on the server. You can call them from the client (with callback) but the execution happens server side.
Also the value doesn't seem to be available after findOne is called,
so it's likely async but findOne has no callback feature.
You don't need to call it twice. See the code below:
Meteor.methods({
'messages.get'(messageId) {
check(messageId, String);
var message = Messages.findOne({_id:messageId});
if (message) {
// Increment views value on current doc
message.views++;
// Update by current doc
Messages.update(messageId,{ $set: { views: message.views }});
}
// return current doc or null if not found
return message;
},
});
You can call that by your client like:
Meteor.call('messages.get', 'myMessageId01234', function(err, res) {
if (err || !res) {
// handle err, if res is empty, there is no message found
}
console.log(res); // your message
});
Two additions here:
You may split messages and views into separate collections for sake of scalability and encapsulation of data. If your publication method does not restrict to public fields, then the client, who asks for messages also receives the view count. This may work for now but may violate on a larger scale some (future upcoming) access rules.
views++ means:
Use the current value of views, i.e. build the modifier with the current (unmodified) value.
Increment the value of views, which is no longer useful in your case because you do not use that variable for anything else.
Avoid these increment operator if you are not clear how they exactly work.
Why not just using a mongo $inc operator that could avoid having to retrieve the previous value?
What could be wrong with this update function?
fixrecs2 = function() {
var arr = myColl.find({ d: 1 }).fetch();
for (var i = 0; i < arr.length; i++) {
var code = arr[i].c;
var rec = myOtherColl.findOne( { cc: code });
if (rec) {
c(rec._id)
myOtherColl.update( {_id: rec._ID }, { $set: {dt: "ant"} } );
}
}
console.log(i + " records processed.");
}
I have never had trouble updating my documents before in this way. Checking the output in the console, I can tell that all the records that I expect to find are there. I can see their _id values printed by console.log(). But the dt field does not get updated. In some cases, the dt field already exists, in some cases it doesn't, but update is supposed to add a field if it's not there, right?
I have tried adding a callback, but it did not seem to run. (I have not been able to find a good callback example for the update function.) In any case, according to the docs, I should get an error message in the console if update fails. I'm still running the insecure package, so there's no allow or deny rules to worry about. I'm really stumped by this!
The issue in this case, turned out to be a simple misspelling, rec._ID is not the same as rec._id. This was likely overlooked due to the common capitalization of MongoDB's ObjectID.
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!
Why on earth is this update failing? This is basic, I've done this many other times.
Meteor.users.update(id, {$set: {lastname: "Archer"}});
That query above has even been simplified from what it originally was, and it's still having the same problem. Originally I was compiling the update programatically:
console.log(obj);
console.log(id);
if (obj) collection.update(id, obj);
Here is my exact output from those lines:
Object {$set: Object}
$set: Object
lastname: "Archer"
__proto__: Object
__proto__: Object
main.js?b9104f881abb80f93da518738bf1bfb4cab0b2b6:68
YXeudfnHyKmsGXaEL
main.js?b9104f881abb80f93da518738bf1bfb4cab0b2b6:69
update failed: MongoError: invalid query
debug.js:41
The id is correct, and if the obj has something wrong with it, it'll be news to me!
Now, I'm quite positive this has nothing to do with my allow function. In the first tests of this part of my program, I actually did get the update not allowed (or whatever it is) error, but I modified my allow function to take this into account, and that error went away.
Here's my allow function. I have a user system where a user can have a student object that "mirrors" it's name information, hence the added complexity. That portion of the function is only engaged in certain circumstances, and it doesn't alter the behavior of the allowance.
Meteor.users.allow({
update: function (userId, doc, fields, modifier) {
var allow = (userId && (doc._id === userId) && _.without(fields,
'firstname', 'lastname', 'student_ids', 'payment_ids', 'phones').length == 0) || Meteor.user().admin;
if (allow && modifier.$set && (_.contains(fields, 'firstname') || _.contains(fields, 'lastname'))) {
var user = Meteor.users.findOne(userId);
var obj = {};
if (modifier.$set.firstname) obj.firstname = modifier.$set.firstname;
if (modifier.$set.lastname) obj.lastname = modifier.$set.lastname;
if (obj) Students.update({_id: {$in: user.student_ids}, reflectsUser: true}, {$set: obj});
}
return allow;
}
});
It turns out my allow function was the problem, but in a bit of a sneaky way.
The Meteor.users.update call wasn't actually the one that was failing, it was this one:
Students.update({_id: {$in: user.student_ids}, reflectsUser: true}, {$set: obj});
I wasn't properly checking that user.student_ids field, so if it was undefined (that user didn't have any students) then the query was invalid. Throwing in a line to vet that array:
var student_ids = user.student_ids || [];
solved the problem.
Since the Meteor error didn't tell me which query was invalid, it led me on a bit of a wild goose chase. Just goes to show that good errors can go a long way!
Also, performing database queries that have other database queries as side-effects is something to be done very carefully.