在使用“$”运算符更新我的集合时,我遇到了这个奇怪的问题。假设以下架构:
var mySchema = {
myArray : [{
name : String
}]
};
以及以下数据(推送到 myArray 时会自动引入 _id 属性)
[{name : 'foo', _id:'1'}, {name: 'bar', _id: '2'}]
以下内容按预期工作,它替换文档而不弄乱 _id 字段:
var newFoo = {name : 'newFoo'};
mySchema.findOneAndUpdate({name : 'foo'}, {'$set' : {'myArray.0': newFoo }}},function(error, doc) {
//the resulting document looks like
//{name : 'newFoo', _id : '1'}
});
但是使用 $ 运算符而不是数组索引,将导致丢失 _id 字段:
mySchema.findOneAndUpdate({name : 'foo'}, {'$set' : {'myArray.$': newFoo }}},function(error, doc) {
//the resulting document looks like
//{name : 'newFoo'}
});
有人遇到过类似的错误吗?
谢谢。
最佳答案
这不是一个“错误”,而是设计使然,你做错了你真正想做的事情,而且这里发生的事情也不是你想象的那样。
在 MongoDB 的“更新”中,语句的“更新”部分中的任何参数都被视为“字面意思”。 $set
运算符的存在使您可以指定一个或多个“要更新的字段”,并且仅更新提到的字段而不是覆盖整个文档。
即使您在此处指定数组字段作为参数,但实际上发生的是“整个”数组元素被“替换”,而不仅仅是您的意图中的“名称”字段。
此列表演示了正在发生的情况:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var testSchema = new Schema({
"list" : [{ "name": String }]
});
var Test = mongoose.model( 'Test', testSchema, "test" );
mongoose.connect('mongodb://localhost/test');
async.series([
function(callback) {
Test.remove({},callback);
},
function(callback) {
var test = new Test({ "list": [{ "name": "foo"},{ "name": "bar" }] });
test.save(function(err,doc) {
console.log(doc);
callback();
});
},
function(callback) {
var newFoo = { "name": "newFoo" };
Test.findOneAndUpdate(
{ "list.name": "foo" },
{
"$set": { "list.0": newFoo }
},
function(err,doc) {
console.log(doc);
callback();
}
)
},
function(callback) {
var newFoo = { "name": "moreFoo" };
Test.findOneAndUpdate(
{ "list.name": "newFoo" },
{
"$set": { "list.$": newFoo }
},
function(err,doc) {
console.log(doc);
callback();
}
);
}
]);
基本上就是你在做什么。现在仔细查看输出:
_id: 546419d60158d92a6f0167d2,
list:
[ { name: 'foo', _id: 546419d60158d92a6f0167d4 },
{ name: 'bar', _id: 546419d60158d92a6f0167d3 } ] }
{ _id: 546419d60158d92a6f0167d2,
__v: 0,
list:
[ { name: 'newFoo', _id: 546419d60158d92a6f0167d5 },
{ name: 'bar', _id: 546419d60158d92a6f0167d3 } ] }
{ _id: 546419d60158d92a6f0167d2,
__v: 0,
list:
[ { name: 'moreFoo' },
{ name: 'bar', _id: 546419d60158d92a6f0167d3 } ] }
这是三次更新的结果,正如你所说,在第三条语句之后,_id
确实丢失了。但请查看第二条语句中的 _id
值。它与原始“不同”,因此新的“子文档”“替换”了该元素,而不是更新“名称”。
这里肯定有一点“有趣”,但它更与“mongoose”如何翻译你的语句有关,而不是与 MongoDB 相关。 _id
值是由 mongoose 创建的,而不是 MongoDB 创建的。 MongoDB 仅为“顶级”文档创建一个 _id
值。 Mongoose 对数组执行此操作,因为它默认认为这是一件好事。
因此,在某些时候, Mongoose “计算出”您的“更新”实际上将“替换”指定索引处的元素,并根据架构规则创建一个新对象作为参数。
但如前所述,这不是$set
本来是要被使用的。如果您只想设置子文档的“名称”属性,而不设置其他属性,则可以使用 "dot notation" 指定该属性的“完整路径”。 .
这是修改后的列表,仅修改“名称”属性,没有其他任何内容:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var testSchema = new Schema({
"list" : [{ "name": String }]
});
var Test = mongoose.model( 'Test', testSchema, "test" );
mongoose.connect('mongodb://localhost/test');
async.series([
function(callback) {
Test.remove({},callback);
},
function(callback) {
var test = new Test({ "list": [{ "name": "foo"},{ "name": "bar" }] });
test.save(function(err,doc) {
console.log(doc);
callback();
});
},
function(callback) {
var newFoo = { "name": "newFoo" };
Test.findOneAndUpdate(
{ "list.name": "foo" },
{
"$set": { "list.0.name": newFoo.name }
},
function(err,doc) {
console.log(doc);
callback();
}
)
},
function(callback) {
var newFoo = { "name": "moreFoo" };
Test.findOneAndUpdate(
{ "list.name": "newFoo" },
{
"$set": { "list.$.name": newFoo.name }
},
function(err,doc) {
console.log(doc);
callback();
}
);
}
]);
将其中的两个符号分别视为 "list.0.name"
和 "list.$.name"
。现在看看其中的区别:
{ __v: 0,
_id: 54641bcfdd89cf2c7299025e,
list:
[ { name: 'foo', _id: 54641bcfdd89cf2c72990260 },
{ name: 'bar', _id: 54641bcfdd89cf2c7299025f } ] }
{ _id: 54641bcfdd89cf2c7299025e,
__v: 0,
list:
[ { name: 'newFoo', _id: 54641bcfdd89cf2c72990260 },
{ name: 'bar', _id: 54641bcfdd89cf2c7299025f } ] }
{ _id: 54641bcfdd89cf2c7299025e,
__v: 0,
list:
[ { name: 'moreFoo', _id: 54641bcfdd89cf2c72990260 },
{ name: 'bar', _id: 54641bcfdd89cf2c7299025f } ] }
现在您会看到,在每种情况下,_id
都保持不变,这是因为“我们告诉更新不要管它”。
这基本上就是你想要的。使用 positional $
时匹配找到的索引位置 mongoose 的运算符“不应该”假设右侧缺少任何参数而没有 "dot notation"意味着应该创建一个新对象。相反,您“应该”指定您希望更改的“仅字段”的整个路径。
关于javascript - mongodb/mongoose - 使用 $ 运算符更新会丢失 _id,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26898022/