1

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.

1 Answer 1

2

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!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.