Mongoose: Model.save () не сохраняет встроенные массивы

Созданный на 10 нояб. 2012  ·  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

Самый полезный комментарий

Я понимаю, в чем проблема - классический случай первого вопроса в FAQ по мангусту.

Спасибо, в моем случае это была именно эта проблема! :)

model.myArray[index] = anyValue

становится

model.myArray.set(index, anyValue)

Все 26 Комментарий

Также укажите код, который вы используете для воспроизведения проблемы (когда вы манипулируете документом перед сохранением и т. Д.)

Конечно, я разместил это в основном, чтобы избежать стены кода здесь, https://gist.github.com/4055392

Я включил как сохранение голоса в событие верхнего уровня (работает), так и сохранение его во вложенном событии (не работает). Как видите, они добавляют или обновляют голосование, а затем сохраняют его аналогичным образом.

Попробуйте исправить ссылку EventSchema, использованную в suggestions :

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

Я протестировал модифицированный массив размеров прямо перед сохранением, учитывая ввод «1111» в слот sizes [0] .lengths [0], и это отображается в документе при проверке в Node, но сохранение возвращается точно тот же документ, что и предварительное сохранение.

Пожалуйста, дайте мне знать, если вам понадобится дополнительная информация.

@ crispen-smith Мне нужно пояснение, там слишком много кода, чтобы я мог легко понять. Можете ли вы включить режим отладки мангуста с помощью require('mongoose').set('debug', true); и опубликовать результат? Это показывает мне запросы и записи, которые отправляются на сервер.

Да, кода много, но в то же время ... недостаточно, чтобы легко его объяснить.

Итак, запуск отладки, похоже, предполагает, что сохранение даже не выполняется, вот сохранение с изменением имени:

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: 'Диаграмма с измерениями'}) {fields: undefined}
(Ага, вот и все ... запроса на обновление нет)

Я попытаюсь исправить это, удаляя каждый раз весь дополнительный документ, по крайней мере, на данный момент ... Мне не нужна ссылочная целостность или идентификаторы объектов для этой конкретной цели.

--ОБНОВИТЬ--
Я пробовал удалить и перезагрузить, но это тоже не работает. Хотя у меня нет трассировки вывода для этого фрагмента, похоже, что удаление также не запускает внутреннюю операцию. Есть ли какая-то грязная проверка, которая не запускается?

Есть ли возможность снова открыть это с учетом моих заметок выше?

@ 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 ()) бросить причину;
^
AssertionError: "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.(/ Пользователи / crispensmith / Documents / mongooseTest / node_modules / mongoose / node_ modules / mpromise / lib / обещание.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, суть заключается в том, что нет проблем с созданием или чтением, установка обновлений завершается ошибкой.

TL; DR;
В моем случае это означает, что после определения набора длин для измерения их нельзя редактировать. Я работаю над проектом розничной торговли модной одеждой с двумя вариантами использования, которые потребуют массивов в суб-документах. Первые варианты использования заключаются в том, что у обычных пользователей будет свой собственный профиль, и они должны иметь возможность поддерживать его. Второй вариант использования заключается в том, что роль администратора может создавать настраиваемые диаграммы для списков для различных типов продуктов (вершины, основания и т. Д.). Обычно я бы не ожидал, что профилю администратора потребуется редактировать эти вторичные диаграммы, но все же было бы неплохо иметь для этого варианта использования.

Теоретически текущая функциональность на самом деле представляет собой хороший (случайный) неизменяемый объект, но объем работы, связанной с его использованием в качестве неизменяемого, а не просто редактированием, нетривиален.

Я понимаю, в чем проблема - классический случай первого вопроса в FAQ по мангусту . Mongoose не может отслеживать изменения, когда вы устанавливаете индекс массива напрямую без прокси-серверов ES6 или ES7 Object.observe() . Использовать

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

или

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

Ладно, в этом есть смысл. Как ни странно (и это может быть связано с моими стратегиями поиска) я смог найти только функции, которые работали 1x за сохранение, и ни одна из них не подошла бы для варианта использования.

Я понимаю, в чем проблема - классический случай первого вопроса в FAQ по мангусту.

Спасибо, в моем случае это была именно эта проблема! :)

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.im или Slack, чтобы поговорить об этом в режиме реального времени 👍

Я использую 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 рейтинги