3
votes

I'm getting a duplicate document when using the mongodb-native-driver to save an update to a document. My first call to save() correctly creates the document and adds a _id with an ObjectID value. A second call creates a new document with a text _id of the original ObjectID. For example I end up with:

> db.people.find()
{ "firstname" : "Fred", "lastname" : "Flintstone", "_id" : ObjectId("52e55737ae49620000fd894e") }
{ "firstname" : "Fred", "lastname" : "Flintstone with a change", "_id" : "52e55737ae49620000fd894e" }

My first call correctly created Fred Flinstone. A second call that added " with a change" to the lastname, created a second document.

I'm using MongoDB 2.4.8 and mongo-native-driver 1.3.23.

Here is my NodeJS/Express endpoint:

app.post("/contacts", function (req, res) {
  console.log("POST /contacts, req.body: " + JSON.stringify(req.body));

  db.collection("people").save(req.body, function (err, inserted) {
    if (err) {
      throw err;
    } else {
      console.dir("Successfully inserted/updated: " + JSON.stringify(inserted));
      res.send(inserted);
    }
  });
});

Here is the runtime log messages:

POST /contacts, req.body: {"firstname":"Fred","lastname":"Flintstone"}
'Successfully inserted/updated: {"firstname":"Fred","lastname":"Flintstone","_id":"52e55737ae49620000fd894e"}'
POST /contacts, req.body: {"firstname":"Fred","lastname":"Flintstone with a change","_id":"52e55737ae49620000fd894e"}
'Successfully inserted/updated: 1'

Why doesn't my second update the existing record? Does the driver not cast the _id value to an ObjectID?

2

2 Answers

4
votes

What you are posting back the 2nd time contains a field named "_id", and it's a string. That is the problem.

Look at the document, what the save method does is a "Simple full document replacement function". I don't use this function quit often so here's what I guess. The function use the _id field to find the document and then replace the full document with what you provided. However, what you provided is a string _id. Apparently it doesn't equal to the ObjectId. I think you should wrap it to an ObjectId before passing to the function.

Besides, the save method is not recommended according to the document. you should use update (maybe with upsert option) instead

1
votes

I don't exactly know why a second document is created, but why don't you use the update function (maybe with the upsert operator)?

An example for the update operation:

var query = { '_id': '52e55737ae49620000fd894e' };

db.collection('people').findOne(query, function (err, doc) {
    if (err) throw err;

    if (!doc) {
        return db.close();
    }

    doc['lastname'] = 'Flintstone with a change';

    db.collection('people').update(query, doc, function (err, updated) {
        if (err) throw err;

        console.dir('Successfully updated ' + updated + ' document!');

        return db.close();
    });
});

And now with the upsert operator:

var query = { '_id': '52e55737ae49620000fd894e' };
var operator = { '$set': { 'lastname': 'Flintstone with a change' } };
var options = { 'upsert': true };

db.collection('people').update(query, operator, options, function (err, upserted) {
    if (err) throw err;

    console.dir('Successfully upserted ' + upserted + ' document!');

    return db.close();
});

The difference is that the upsert operator will update the document if it exist, otherwise it will create a new one. When using the upsert operator you should keep in mind that this operation can be underspecified. That means if your query does not contain enough information to identify a single document, a new document will be inserted.