Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB update subdocument changes its _id

I have the next document, and I want to update the addresses with id of 21, change the alias to 'Family'. I run User.update({ _id: 2, 'addresses._id': 21 }, { 'addresses.$': newAddress });

Which works fine, with an annoying side effect, is that Mongo generates a new id for the subdocument. Is there any way to update a subdocument without getting a new id?

'user': {
  '_id': 2,
  'addresses': [
    {
      '_id': '20',
      'alias': 'Work',
      'postal_code': 1235
    },
    {
      '_id': '21',
      'alias': 'Home',
      'postal_code': 1235
    }
  ]
}

I already solved this using

User.update(
  { _id: req.user._id, 'addresses._id': addressId },
  { $set: {
    'addresses.$.alias': newAddress.alias,
    'addresses.$.address': newAddress.address,
    'addresses.$.areaId': newAddress.areaId,
    'addresses.$.cityId': newAddress.cityId,
    'addresses.$.postal_code': newAddress.postal_code
  } }
);

This doesn't change the id of the subdocument, but I don't like this solution for obvious reasons.

like image 300
Hafez Avatar asked Jan 28 '23 18:01

Hafez


2 Answers

Adding to JasonCust's answer, the correct approach is to inject the old id into the new address to keep the id unchanged and avoid having to enter each individual key.

Below is an example:

  const userSchema = new Schema({
    addresses: [{ country: String }]
  });

  const User = mongoose.model('User', userSchema);
  const user = await User.create({
    addresses: [
      { country: 'Egypt' }
    ]
  });

  const oldAddressId = user.addresses[0]._id;

  //
  const newAddress = {
    _id: oldAddressId,
    country: 'Japan'
  };

  await User.updateOne({ 'addresses._id': oldAddressId }, { 'addresses.$': newAddress });
  const userAfterUpdate = await User.findOne({ _id: user._id });

  assert.equal(userAfterUpdate.addresses[0].country, 'Japan');
  assert.equal(userAfterUpdate.addresses[0]._id.toString(), oldAddressId.toString());
like image 74
Hafez Avatar answered Jan 31 '23 09:01

Hafez


The short answer is: no. In essence the update object { 'addresses.$': newAddress } is a command to replace the entire object at the matched pointer location with the newAddress object. That said, if the newAddress object includes the _id value then it should be stored as the value.

like image 41
Jason Cust Avatar answered Jan 31 '23 08:01

Jason Cust