问题
{
_id:xxxxstoreid
store:{
products:[
{
_id:xxxproductid,
name:xxx,
img:url,
}
]
}
}
since i cannot predict the request for update, params may have just name or it may have both.
here is my query,it updates successfully but it removes other fields if they are not present in the params. eg:
var params={_id:xxid,name:'xxx',img:'xxx'}
or
var params={_id:xxid,name:'xxx'}
in this case if params have just name it removes img field and updates.
User.update({'store.products._id':params._id},{$set:{"store.products":params}},callback);
回答1:
You need to supply the multiple keys to $set with the positional $ operator to update both matched keys.
I prefer the modern ES6 way of object manipulation:
let params = { "_id" : "xxxproductid", "name" : "xxx", "img" : "yyy" };
let update = [
{ 'store.products._id': params._id },
{ "$set": Object.keys(params).filter(k => k != '_id')
.reduce((acc,curr) =>
Object.assign(acc,{ [`store.products.$.${curr}`]: params[curr] }),
{ })
}
];
User.update(...update,callback);
Which would produce the call to MongoDB as ( with mongoose.set('debug', true)
) turned on so we see the request:
Mongoose: users.update({ 'store.products._id': 'xxxproductid' }, { '$set': { 'store.products.$.name': 'xxx', 'store.products.$.img': 'yyy' } }, {})
Where basically you take your input params
and supply the _id
as the first argument for the "query" :
{ 'store.products._id': params._id },
The rest takes the "keys" from the object via Object.keys which makes an "array" which we can "filter" with Array.filter() and then pass to Array.reduce to transform those keys into an Object
.
Inside the .reduce() we call Object.assign() which "merges" objects with the given keys, generated in this form:
Object.assign(acc,{ [`store.products.$.${curr}`]: params[curr] }),
Using the template syntax to assign the "current" (curr) "key" into the new key name, again using the ES6 key assignment syntax []: which allows variable names in object literals.
The resulting "merged" object is passed back to be assigned to the "root" object where $set
is used for the key of the update, so the "generated" keys are now children of that.
I use an array for the arguments purely for debugging purposes, but then that also allows cleaner syntax on the actual .update()
using the "spread" ...
operator to assign the arguments:
User.update(...update,callback);
Clean and simple, and some JavaScript techniques that you should learn for object and array manipulation. Mostly since the MongoDB query DSL is basically "Objects" and "Arrays". So learn to manipulate them.
回答2:
function updateProducts(params) {
var query = {'store.products': {$elemMatch: {_id: params._id}}}
var updateObject = null;
if (params.name && params.img) {
updateObject = {$set: {'store.products.$': params}}
} else if(params.name && !params.img) {
updateObject = {$set: {'store.products.$.name': params.name}}
} else if (params.img && !params.name) {
updateObject = {$set: {'store.products.$.img': params.img}}
}
User.update(query, updateObject, callback)
}
回答3:
The below query will use $ positional operator to locate the array element at index found from matching the array by _id
in the query document followed by updating the fields for the element with the params
values.
var params = {_id:1, name:'xxx',img:'yyy'};
var id = params['_id']; // Get id to locate matching array index
delete params['_id']; // Remove id from the object
Object.keys(params).forEach(function (key){params['store.products.$.'+key] = params[key]; delete params[key];}) // Transforms remaining object keys to include the positional $ placeholder to { "store.products.$.name" : "xxx", "store.products.$.img" : "yyy" }
User.update('store.products._id':id},{$set:params},callback);
来源:https://stackoverflow.com/questions/44850822/how-to-update-multiple-fields-of-an-array-object-with-one-request