简体   繁体   中英

Mongoose overwriting data in MongoDB with default values in Subdocuments

I currently have a problem with updating data in MongoDB via mongoose. I have a nested Document of the following structure

const someSchema:Schema = new mongoose.Schema({
  Title: String,
  Subdocuments: [{
    SomeValue: String
    Position: {
     X: {type: Number, default: 0},
     Y: {type: Number, default: 0},
     Z: {type: Number, default: 0}
    }
  }]  
});

Now my problem is that I am updating this with findOneAndUpdateById. I have previously set the position to values other than the default. I want to update leaving the position as is by making my request without the Position as my frontend should never update it (another application does).

However the following call

const updateById = async (Id: string, NewDoc: DocClass) => {
    let doc: DocClass | null = await DocumentModel.findOneAndUpdate(
        { _id: Id },
        { $set: NewDoc },
        { new: true, runValidators: true });
    if (!doc) {
        throw createError.documentNotFound(
          { msg: `The Document you tried to update (Id: ${Id}) does not exist` }
        );
    }
    return doc;
}

Now this works fine if I don't send a Title for the value in the root of the schema (also if i turn on default values for that Title) but if I leave out the Position in the Subdocument it gets reset to the default values X:0, Y:0, Z:0.

Any ideas how I could fix this and don't set the default values on update?

Why don't you find the document by id, update the new values, then save it?

const updateById = async (Id: string, NewDoc: Training) => {
    const doc: Training | null = await TrainingModel.findById({ _id: Id });
    if (!doc) {
      throw createError.documentNotFound(
        { msg: `The Document you tried to update (Id: ${Id}) does not exist` }
      );
    }
    doc.title = NewDoc.title;
    doc.subdocument.someValue = NewDoc.subdocument.someValue
    await doc.save();
    return doc;
}

check out the link on how to update a document with Mongoose https://mongoosejs.com/docs/documents.html#updating

Ok after I gave this some thought over the weekend I got to the conclusion that the behaviour of mongodb was correct.

Why?

I am passing a document and a query to the database. MongoDb then searches Documents with that query. It will update all Fields for which a value was supplied. If for Title I set a new string, the Title will get replaced with that one, a number with that one and so on. Now for my Subdocument I am passing an array. And as there is no query, the correct behavioud is that that field will get set to the array. So the subdocuments are not updated but indeed initialized. Which will correctly cause the default values to be set. If I just want to update the subdocuments this is not the correct way

How to do it right

For me the ideal way is to seperate the logic and create a seperate endpoint to update the subdocuments with their own query. So to update all given subdocuments the function would look something like this

const updateSubdocumentsById= async ({ Id, Subdocuments}: { Id: string; Subdocuments: Subdocument[]; }): Promise<Subdocument[]> => {
    let updatedSubdocuments:Subdocument[] = [];

    for (let doc of Subdocuments){
        // Create the setter
        let set = {};
        for (let key of Object.keys(doc)){
            set[`Subdocument.$.${key}`] = doc[key];
        }

        // Update the subdocument
        let updatedDocument: Document| null = await DocumentModel.findOneAndUpdate(
            {"_id": Id, "Subdocuments._id": doc._id},
            {
                "$set" : set
            },
            { new : true}
        );

        // Aggregate and return the updated Subdocuments
        if(updatedDocument){
            let updatedSubdocument:Subdocument = updatedTraining.Subdocuments.filter((a: Subdocument) => a._id.toString() === doc._id)[0];
            if(updatedSubdocument) updatedSubdocuments.push(updatedSubdocument);
        }
    }
    return updatedSubdocuments;
}


Been struggling with this myself all evening. Just worked out a really simple solution that as far as I can see works perfectly.

const venue = await Venue.findById(_id)
  venue.name = name
  venue.venueContact = venueContact

  venue.address.line1 = line1 || venue.address.line1
  venue.address.line2 = line2 || venue.address.line2
  venue.address.city = city || venue.address.city
  venue.address.county = county || venue.address.county
  venue.address.postCode = postCode || venue.address.postCode
  venue.address.country = country || venue.address.country
  venue.save()

  res.send(venue)

The result of this is any keys that don't receive a new value will just be replaced by the original values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM