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

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

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

谢谢。

最佳答案

这不是一个“错误”,而是设计使然,你做错了你真正想做的事情,而且这里发生的事情也不是你想象的那样。

在 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/

相关文章:

javascript - Onclick 不同的 DIV 标签

javascript - Mongoose 'or' 和 'like' 运营商 node.js

java - 以两种不同的方式序列化 jackson 场

java - MongoDB - Morphia 嵌入式

javascript - 使用 ReactJs 在搜索列表中突出显示匹配的字母/单词

javascript - 移动 YouTube 页面如何在 iOS 上自动播放视频?

javascript - 将图像上传到 Canvas 并在新选项卡中显示

node.js - 语法错误: Unterminated quoted string in phantomjs

node.js - 实现yarn进行依赖管理,并运行yarn升级 - 获取生命周期警告并且构建现在失败

javascript - 如何在 Node.js 应用程序中处理 mongoose 中的错误