Mongoose: Model.save() 不保存嵌入的数组

创建于 2012-11-10  ·  26评论  ·  资料来源: Automattic/mongoose

我有嵌套的文档数组,当我更改它们并在模型上执行 .save() 时,它不会保存嵌套文档中的更改。

这是我的模型和模式(为简洁起见省略了一些):

var Votes = new mongoose.Schema({
    date: Date, 
    user_name: String,
    is_up: Boolean
});

var EventSchema = new mongoose.Schema({
  to_date: Date,
  from_date: Date,
  location: String,
  name: String,
  suggested_by: String,
  description: String,
  users: [EventUser],
  comments: [Comments],
  suggestions: [Event],
  votes: [Votes]
});

var Event = mongoose.model('Event', EventSchema);

event.save() 之前的模型调用:

{ 
     __v: 1,
    _id: 509e87e583ccbfa00e000004,
    description: 'Pfft',
    from_date: Sun Nov 11 2012 08:00:00 GMT-0500 (EST),
    location: 'Home',
    name: 'Whatever',
    suggested_by: 'No one',
    to_date: Sun Nov 11 2012 00:00:00 GMT-0500 (EST),
    votes: [],
    suggestions: 
     [ { users: [],
         comments: [],
         suggestions: [],
         votes: [],
         _id: 509e880883ccbfa00e000005,
         suggested_by: 'Some one',
         to_date: Sun Nov 11 2012 04:00:00 GMT-0500 (EST),
         from_date: Mon Nov 12 2012 00:00:00 GMT-0500 (EST),
         location: 'Home',
         name: 'Football',
         description: 'FOOTBALL!!' } ],
    comments: [],
    users: [] 
}

调用event.save()之前具有嵌套投票的同一对象。

{
   "__v":1,
   "_id":"509e87e583ccbfa00e000004",
   "description":"Pfft",
   "from_date":"2012-11-11T13:00:00.000Z",
   "location":"Home",
   "name":"Whatever",
   "suggested_by":"No one",
   "to_date":"2012-11-11T05:00:00.000Z",
   "votes":[ ],
   "suggestions":
      [ {
         "users":[],
         "comments":[ ],
         "suggestions":[ ],
         "votes":
            [{
               "is_up":true,
               "date":"2012-11-10T18:05:25.796Z",
               "user_name":"No one"
            }],
         "_id":"509e880883ccbfa00e000005",
         "suggested_by":"Some one",
         "to_date":"2012-11-11T09:00:00.000Z",
         "from_date":"2012-11-12T05:00:00.000Z",
         "location":"Home",
         "name":"Football",
         "description":"FOOTBALL!!"
      }],
   "comments":[],
   "users":[]
}

调用 event.save() 时,不会抛出任何错误,但实际上并未保存嵌套事件模式中的嵌套投票模式。 如果我在顶级事件对象中使用相同的整体逻辑来保存投票,它确实有效。

当我查看代码时,简单地说, .save() 似乎是保存新对象以及更新已存在对象的快捷方式。

我的预感是 Model.prototype._delta 不够深入,无法捕获所有嵌套对象, https://github.com/LearnBoost/mongoose/blob/master/lib/model.js#L529

最有用的评论

我明白问题是什么 - 猫鼬常见问题解答中第一个问题的经典案例。

谢谢,在我的情况下就是这个问题! :)

model.myArray[index] = anyValue

变成

model.myArray.set(index, anyValue)

所有26条评论

还请包括您用来重现问题的代码(您在保存之前操作文档的位置等)

当然,我将它发布在一个要点中以避免这里的代码墙, https://gist.github.com/4055392

我包括在顶级事件中保存投票(工作)以及将其保存在嵌套事件中(不工作)。 如您所见,他们会添加或更新投票,然后以类似的方式保存。

尝试修复suggestions使用的 EventSchema 引用:

// bad
var EventSchema = new mongoose.Schema({
  suggestions: [EventSchema] <== at this time, EventSchema is undefined which is interpreted as Mixed

// instead...

var EventSchema = new mongoose.Schema;
EventSchema.add({
  suggestions: [EventSchema] <== EventSchema exists
})


关闭,没有反应。 如有必要,请重新打开。

我有同样的问题,其中 .save() 返回一个已更新嵌入对象的文档,但它实际上并未保存在 mongodb 中。 请帮我解决这个问题。

@Manojkumar91你能提供一些重现这个的代码吗? 会很有帮助:)

我也遇到过这种情况,这是我的模型:

/*jslint node:true */
"use strict";

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var measurement = new Schema({
    name: {type: String, required: true},
    instruction: {type: String, required: true}
});

var size = new Schema({
    name: {type: String, required: true},
    lengths: [String]
});

var chartSchema = new Schema({
    name: {type: String, required: true },
    diagram: {type: String, required: true },
    description: {type: String, required: true},
    totalMeasurements: {type: Number, required: true},
    totalSizes: {type: Number, required: true},
    measurements: [measurement],
    sizes: [size]
});

module.exports = mongoose.model('chart', chartSchema);

这是一个更大的应用程序的一部分,所以我只包括相关的控制器操作过滤器,但我可以从更大的意义上讲其余部分。 下面是过滤器代码:

    module.exports = function (next) {
      var paramCounter = 1,
          outerLoopCounter = 0,
          existingSizes = this.chart.sizes.length,
          outerLoopEdge = this.chart.totalSizes,
          innerLoopCounter = 0,
          innerloopEdge = this.chart.totalMeasurements - 1,
          size = {name: null, lengths: [] };

      if (this.req.method && this.req.method === "POST") {
          for (outerLoopCounter;
                  outerLoopCounter < existingSizes;
                  outerLoopCounter = outerLoopCounter + 1) {

              this.chart.sizes[outerLoopCounter].name =
                  this.param("name_" + paramCounter);

              for (innerLoopCounter;
                      innerLoopCounter <= innerloopEdge;
                      innerLoopCounter = innerLoopCounter + 1) {
                  this.chart.sizes[outerLoopCounter].lengths[innerLoopCounter] =
                      this.param(this.chart.measurements[innerLoopCounter].name);
              }
              paramCounter = paramCounter + 1;
              innerLoopCounter = 0;
          }

        for (outerLoopCounter;
                outerLoopCounter < outerLoopEdge;
                outerLoopCounter = outerLoopCounter + 1) {
            size.name = this.param("name_" + paramCounter);
            for (innerLoopCounter;
                    innerLoopCounter < innerloopEdge;
                    innerLoopCounter = innerLoopCounter + 1) {
                size.lengths.push(
                    this.param(this.chart.measurements[innerLoopCounter].name
                               + "_" + paramCounter)
                );
            }
            this.chart.sizes.push(size);
            paramCounter = paramCounter + 1;
            innerLoopCounter = 0;
            size = { name: null, lengths: [] };
        }

        this.chart.save(function (err) {
            if (err) {
                console.log(err);
            }

            this.chart_info = "measurements for <strong>" + this.chart.name + "</strong> saved.";
            this.render("display");
        }.bind(this));
    } else {
        next();
    }
  };

实际上,在运行此过滤器之前,会调用过滤器,该过滤器基于二维测量值和大小数组创建状态相关的表单结构,然后解析此元素列表以提供更新。 我一直在用虚拟数据进行测试,当前的虚拟图表(取自控制台)如下所示:

> db.charts.find();
{ "_id" : ObjectId("553da6c3d3d0940a640e878c"), "name" : "Chart With Measurements", "diagram" : "http://res.cloudinary.com/harung71k/image/upload/v1430103747/nd4gipcxnykbbcpcztp9.jpg", "description" : "<p>This is a test of the new measurement methodology, it works.</p>", "totalMeasurements" : 4, "totalSizes" : 3, "sizes" : [ { "name" : "Small", "_id" : ObjectId("554183ed63c5945b73b8a8e7"), "lengths" : [ "1", "2", "3", "4" ] }, { "name" : "Medium", "_id" : ObjectId("554183ed63c5945b73b8a8e8"), "lengths" : [ "5", "6", "7", "8" ] }, { "name" : "Large", "_id" : ObjectId("554183ed63c5945b73b8a8e9"), "lengths" : [ "9", "10", "11", "12" ] } ], "measurements" : [ { "name" : "Fuzz", "instruction" : "<p>Fuzz Instructions</p>", "_id" : ObjectId("553dadd253eb9f996c68a381") }, { "name" : "Buzz", "instruction" : "<p>Buzz Instructions</p>", "_id" : ObjectId("553dadd253eb9f996c68a382") }, { "name" : "Beatles", "instruction" : "<p>Beatles Instructions</p>", "_id" : ObjectId("553dadd253eb9f996c68a383") }, { "name" : "Stones", "instruction" : "<p>Stones instructions</p>", "_id" : ObjectId("553ee7a09ff8c567004bd261") } ], "__v" : 3 }

我已经在保存之前测试了修改后的大小数组,在 size[0].lengths[0] 插槽中输入了“1111”,这在 Node 中检查时显示在文档中,但保存正在返回与预先保存相同的文件。

如果您需要任何其他详细信息,请告诉我。

@crispen-smith 我需要澄清一下,代码太多了,我无法轻松掌握。 你能用require('mongoose').set('debug', true);启用猫鼬的调试模式并发布输出吗? 这向我展示了发送到服务器的查询和写入。

是的,这是很多代码,但同时......还不足以轻松解释它。

因此,运行 debug 似乎表明保存甚至没有被命中,这是一个名称更改的保存:

Mongoose: charts.findOne({ name: 'Chart With Measurements' }) { fields: undefined }  
Mongoose: charts.update({ _id: ObjectId("553da6c3d3d0940a640e878c"), __v: 3 }) { '$set': { 'sizes.0.name': 'Smaller' } } {} 

这是运行相同的操作序列,但更改了字符串数组中的名称和值:

Mongoose: charts.findOne({ name: 'Chart With Measurements' }) { fields: undefined }  
Mongoose: charts.update({ _id: ObjectId("553da6c3d3d0940a640e878c"), __v: 3 }) { '$set': { 'sizes.0.name': 'Small' } } {} 

并且,仅对数组内的变量进行更改:

Mongoose: charts.findOne({ name: 'Chart With Measurements' }) { fields: undefined }
(是的,就是这样......没有更新查询)

我将尝试通过每次删除整个子文档来修补它,至少目前是这样......我不需要引用完整性或用于此特定目的的对象 ID。

- 更新 -
我试过删除和重新加载,但这似乎也不起作用。 虽然我没有这件作品的输出跟踪,但看起来删除也不会触发后端操作。 是否有某种未触发的脏检查?

鉴于我上面的笔记,有什么选择可以重新打开它吗?

@crispen-smith 这是我将问题作为独立脚本重现的基本尝试:

var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');
var assert = require('assert');

mongoose.connect('mongodb://localhost:27017/gh1204');

var Schema = mongoose.Schema;

var measurement = new Schema({
    name: {type: String, required: true},
    instruction: {type: String, required: true}
});

var size = new Schema({
    name: {type: String, required: true},
    lengths: [String]
});

var chartSchema = new Schema({
    measurements: [measurement],
    sizes: [size]
});

var Chart = mongoose.model('gh1204', chartSchema);

Chart.create({}, function(error, chart) {
  assert.ifError(error);
  chart.sizes.push({ name: 'bacon', lengths: ['25'] });
  chart.save(function(error, chart) {
    assert.ifError(error);
    assert.equal(chart.sizes[0].lengths.length, 1);
    assert.equal(chart.sizes[0].lengths[0], '25');
    console.log('done');
    process.exit(0);
  });
});

到目前为止没有骰子,它按预期运行。 你能修改上面的例子来演示你看到的问题吗? 我无法将您的散文描述翻译成代码。

当然,今晚晚些时候我会看看这个。

试试这个?

/*jslint node:true */
"use strict";

var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');
var assert = require('assert');

mongoose.connect('mongodb://localhost:27017/gh1204');

var Schema = mongoose.Schema;

var measurement = new Schema({
  name: {type: String, required: true},
  instruction: {type: String, required: true}
  });

var size = new Schema({
  name: {type: String, required: true},
  lengths: [String]
    });

var chartSchema = new Schema({
  measurements: [measurement],
  sizes: [size]
    });

var Chart = mongoose.model('gh1204', chartSchema);

Chart.create({}, function (error, chart) {
    assert.ifError(error);
    chart.sizes.push({ name: 'bacon', lengths: ['25'] });
    chart.save(function (error, chart) {
      assert.ifError(error);
     assert.equal(chart.sizes[0].lengths.length, 1);
     assert.equal(chart.sizes[0].lengths[0], '25');
     console.log('Created Index');

    chart.sizes[0].lengths[0] = "20";

  chart.save(function (error, chart) {
    assert.ifError(error);
    assert.equal(chart.sizes[0].lengths.length, 1);
    assert.equal(chart.sizes[0].lengths[0], '25');
    console.log('Created Index');
    process.exit(0);

    });
  });
});

这是我的节点控制台中的控制台输出:

Crispens-MacBook-Pro:mongooseTest crispensmith$ node index.js
Mongoose: gh1204.insert({ _id: ObjectId("5580d0c44d32b07971dfd281"), sizes: [], measurements: [], __v: 0 })   
Mongoose: gh1204.update({ __v: 0, _id: ObjectId("5580d0c44d32b07971dfd281") }) { '$set': { measurements: [] }, '$pushAll': { sizes: [ { name: 'bacon', _id: ObjectId("5580d0c44d32b07971dfd282"), lengths: [ '25' ] } ] }, '$inc': { __v: 1 } }  
Created Index
Mongoose: gh1204.update({ __v: 1, _id: ObjectId("5580d0c44d32b07971dfd281") }) { '$set': { measurements: [] }, '$inc': { __v: 1 } }  

/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:108
if (this.ended && !this.hasRejectListeners()) 抛出原因;
^
断言错误:“20”==“25”
在 EventEmitter 处。(/Users/crispensmith/Documents/mongooseTest/index.js:44:14)
在 EventEmitter 处。(/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:175:45)
在 EventEmitter.emit (events.js:98:17)
在 Promise.safeEmit (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:81:21)
在 Promise.fulfill (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:94:24)
在 Promise.resolve (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/promise.js:113:23)
在模型。(/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/document.js:1569:39)
在 next_ (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/hooks-fixed/hooks.js:89:34)
在 EventEmitter.fnWrapper (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/hooks-fixed/hooks.js:171:15)
在 EventEmitter 处。(/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:175:45)
在 EventEmitter.emit (events.js:98:17)
在 Promise.safeEmit (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:81:21)
在 Promise.fulfill (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:94:24)
在 p1.then.then.self.isNew (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/model.js:254:27)
在 newTickHandler (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:229:18)
在 process._tickCallback (node.js:442:13)

(对不起,一直有一段时间把它变成适当的降价,这是我能做的最好的。)
这是 mongoDB 的输出:

  > db.gh1204.find();
  { "_id" : ObjectId("5580d0c44d32b07971dfd281"), "sizes" : [ { "name" : "bacon", "_id" : ObjectId("5580d0c44d32b07971dfd282"), "lengths" : [ "25" ] } ], "measurements" : [ ], "__v" : 2 }

从 CRUD 的角度来看,其本质是创建或读取没有问题,放置更新失败。

TLDR;
在我的用例中,这意味着一旦为测量定义了一组长度,就无法对其进行编辑。 我正在处理一个时尚零售项目,它有两个用例,需要子文档中的数组。 第一个用例是普通用户将拥有自己的配置文件,并且应该能够维护这些配置文件。 第二个用例是管理员角色可以为不同产品类型(顶部、底部等)的列表创建自定义图表。 我通常不希望管理员配置文件需要编辑这些辅助图表,但对于该用例来说仍然很好。

从理论上讲,当前的功能实际上提供了一个不错的(偶然的)不可变对象,但是将其用作不可变对象而不仅仅是编辑所涉及的工作量是非常重要的。

我明白问题是什么 -猫鼬常见问题解答第一个问题的经典案例。 当您在没有 ES6 代理或 ES7 Object.observe()类的东西的情况下直接设置数组索引时,Mongoose 无法跟踪更改。 用

chart.sizes[0].lengths.set(0, "20");

或者

chart.sizes[0].lengths[0] = '20';
chart.markModified('sizes.0.lengths.0');

好吧,确实有道理。 奇怪的是,(这可能归因于我的搜索策略)我只能找到每次保存运行 1 次的函数,而这些函数都不适合用例。

我明白问题是什么 - 猫鼬常见问题解答中第一个问题的经典案例。

谢谢,在我的情况下就是这个问题! :)

model.myArray[index] = anyValue

变成

model.myArray.set(index, anyValue)

这对我来说也是个问题,我希望我早点看到这个帖子!

如果您不确定猫鼬是否会收到更改通知,您可以使用

doc.markModified('propChanged')
doc.save() // works

我也用 Array.set() 解决了我的问题。
:+1:

得到同样的错误,这是我的代码片段
image
这是猫鼬日志
image
这是我从邮递员那里得到的
image

哪里不对了?

++ 编辑 ++

  1. 尝试使用Array.set,得到相同的结果

我认为更好的问题是是否有任何正确的事情。 您正在if (req.body.invite != [])块中进行异步保存,然后在解析回调中修改invited数组。 nRoom.invited.push()调用将始终发生在第二次 save() 调用之后。 此外, req.body.invite != []将始终为真,因为[] == []在 JS 中为假。

@vkarpov15哈哈 好的,谢谢你的回答! 👍 有很多东西要学:)

@peterkrieg谢谢!

您好,我在更新深度嵌入的文档时遇到了这个问题,有人有什么建议吗? 我的代码在:

https://stackoverflow.com/questions/51426326/updating-deeply-embedded-documents-with-mongodb-2-6-12

@thehme你用的是什么版本的猫鼬?

在这条线上, {$set: {["dT." + index +".ts." + i + ".th"]: newValue}} ,方括号对我来说不合适。 改用{$set: { "dT." + index +".ts." + i + ".th": newValue } }什么区别吗?

$set docs 只显示一个字符串

随时加入我们的Gitter.imSlack实时讨论它👍

我正在使用 5.5.11 并且仍然有这个问题。 我已经尝试了此线程上提出的所有解决方案,但都没有成功。 我的文档布局如下所示:

{
  myObj: {
    myArr: [{ key: "value" }]
  }
}

myDoc.myObj.myArr.push({ key: "value2" });
// debugging myDoc shows new embedded doc
await myDoc.save();
// console shows new embedded doc is gone

编辑:升级到最新的 (5.7.6) 并且仍然有问题。

下面的脚本在 Mongoose 5.7.6 上运行良好。 请打开一个新问题并按照问题模板进行操作。

const mongoose = require('mongoose');

run().catch(err => console.log(err));

async function run() {
  await mongoose.connect('mongodb://localhost:27017/test', {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });
  await mongoose.connection.dropDatabase();

  const schema = mongoose.Schema({ 
    myObj: { myArr: [{ key: String }] }
  });
  const Model = mongoose.model('Test', schema);


  await Model.create({ myObj: { myArr: [{ key: 'value' }] } });
  const myDoc = await Model.findOne();

  myDoc.myObj.myArr.push({ key: "value2" });
  await myDoc.save();

  console.log(myDoc.myObj.myArr);

  const saved = await Model.findOne();
  console.log(myDoc.myObj.myArr);
}
此页面是否有帮助?
0 / 5 - 0 等级