Getting the old and new version of the document when updating it
Asked Answered
H

1

8

For every operation my application does on MongoDB I want to have the old and new version of the document so I can emit an event with both version:

{
  type: 'UPDATE',
  before: documentBeforeUpdate,
  after: documentAfterUpdate
}

The way I do this right now is to first issue a findOne with the query, then do a findOneAndUpdate with the update, but using the document's _id for the query. So if the query is actually inducing load on the database I'm not paying that price twice:

async function updateOne(query, updates) {
  const oldDocument = await this.model
    .findOne(query, null, { lean: true })
    .exec();

  if (!oldDocument) {
    return;
  }

  const newDocument = await this.model
    .findOneAndUpdate({ _id: oldDocument._id }, updates, {
      new: true,
      lean: true
    })
    .exec();

  // document vanished before it could be updated
  if (!newDocument) {
    return;
  }

  await this.emit("UPDATE", {
    before: oldDocument,
    after: newDocument,
    type: "UPDATE"
  });

  return newDocument;
}

I have similar functions for updateMany, delete{One,Many}, createOne etc.

Now my question is if there is a more performant way than doing that?

Context

What I want to do is to decouple code that would denormalize data in the database for query-performance reasons. Assuming I have an application where you can reserve tables in a restaurant, then I want the reservations to be in there own collection, but I also want to have the availability-information for each table cached in the table's own document. So I can query the table's collection for tables available at a specific time.

// reservation
{
  _id: ObjectId,
  table: ObjectId,
  from: Date,
  to: Date
}

// table
{
  _id: ObjectId,
  reservations: [
  { _id: ObjectId, from: Date, to: Date },
  // ...
  ]
}

When having an event system where I can listen for creates, updates and deletes of documents, I don't need to call the code that is updating the table's reservation property directly from the code that is updating the reservation document. This is the architecture I want to achieve.

Hyo answered 17/4, 2018 at 10:11 Comment(0)
S
-3

There is an option in findAndOneUpdate called

returnNewDocument: boolean

This option will tell to mongodb to return old docs instead of returning updated docs in the response object.

From mongo docs

db.collection.findOneAndUpdate(filter, update, options)

Updates a single document based on the filter and sort criteria.

The findOneAndUpdate() method has the following form:

db.collection.findOneAndUpdate(
   <filter>,
   <update>,
   {
       projection: <document>,
       sort: <document>,
       maxTimeMS: <number>,
       upsert: <boolean>,
       returnNewDocument: <boolean>,
       collation: <document>
   }
)

Noew to your code: Your update object called 'updates' and passed to findAndUpdateOne should include field {returnNewDocument: true}, and then you need to read the response from the findAndUpdateOne method and that way you get your old document without running separate findOne query.

Mongo manual - findOneAndUpdate https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/

Semiweekly answered 8/1, 2019 at 7:20 Comment(2)
AFAIK OP wants to receive both the original and new document with findOneAndUpdate. This answer suggests that passing returnNewDocument: true as part of the update object (not options - so we'll potentially be overwriting a field "returnNewDocument" in the document itself) will return the original document as well as the new one? This doesn't seem right, could you elaborate?Pronunciamento
@Pronunciamento returnNewDocument is an option. @Semiweekly is indeed saying to include returnNewDocument in the update object (Your update object called 'updates' and passed to findAndUpdateOne should include field {returnNewDocument: true}), but that is wrong. Instead, it belongs inside the options object (third parameter for the findOneAndUpdate method). As for returning both documents, AFAIK that isn't possible, you either choose to receive the original/old version of the document or the new updated version of the document.Yate

© 2022 - 2024 — McMap. All rights reserved.