mongodb / mongoose - 使用$运算符更新丢失_id

时间:2014-11-12 22:37:21

标签: javascript node.js mongodb mongoose mongodb-query

我在使用' $'更新我的收藏时看到了这个奇怪的问题。运营商。假设以下架构:

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'}
});

有没有人遇到过类似的错误?

谢谢。

1 个答案:

答案 0 :(得分:2)

这不是设计上的“错误”,你做错了你真正想要做的事情,以及这里发生的事情并不是你想的。

在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。如果您只想设置“name”属性,而不想设置子文档的其他属性,则使用"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"的情况下缺少右侧的任何参数意味着应该创建一个新对象。相反,你“应该”指定你想要改变的“只是一个或多个领域”的整个路径。