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

最も参考になるコメント

問題が何であるかがわかります-マングースFAQの最初の質問の典型的なケース。

おかげで、私の場合はこの問題でした! :)

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

実際には、このフィルターを実行する前に、測定値とサイズの2次元配列に基づいて状態依存のフォーム構造を作成するフィルターが呼び出され、この要素のリストが解析されて更新が提供されます。 私はダミーデータでテストしてきましたが、現在のダミーチャート(コンソールから取得)は次のようになります。

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

サイズ[0] .lengths [0]スロットに「1111」の入力が与えられた場合、保存の直前に変更されたサイズの配列をテストしました。これはノードで検査するとドキュメントに表示されますが、保存は正確に返されます。事前保存と同じドキュメント。

さらに詳しい情報が必要な場合はお知らせください。

@ crispen-smithいくつかの説明が必要です。コードが多すぎて、簡単に把握できません。 require('mongoose').set('debug', true);でmongooseのデバッグモードを有効にして、出力を投稿できますか? これは、サーバーに送信されているクエリと書き込みを示しています。

うん、それはたくさんのコードですが、同時に...それを簡単に説明するには十分ではありません。

したがって、デバッグを実行すると、保存がヒットしないことを示唆しているようです。名前を変更した保存を次に示します。

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' } } {} 

そして、配列内の変数への変更のみで:

マングース: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())throw reason;
^
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で。(/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の観点から見ると、その本質は、CreateまたはReadsに問題がなく、putUpdatesが失敗しているということです。

TLDR;
私のユースケースでは、これは、測定値の長さのセットが定義されると、それらを編集できないことを意味します。 私は、サブドキュメント内に配列を必要とする2つのユースケースを持つファッション小売プロジェクトに取り組んでいます。 最初の使用例は、バニラユーザーが独自のプロファイルを持ち、これらを維持できる必要があることです。 2番目の使用例は、管理者ロールがさまざまな製品タイプ(トップ、ボトムなど)のリストのカスタムチャートを作成できることです。 通常、管理者プロファイルでこれらのセカンダリチャートを編集する必要があるとは思いませんが、そのユースケースではそれでもよいでしょう。

理論的には、現在の機能は実際には素晴らしい(偶発的な)不変オブジェクトを提示しますが、編集だけでなく不変として使用するために必要な作業量は簡単ではありません。

問題が何であるかがわかります-マングースFAQの最初の質問の典型的なケース。 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回動作する関数しか見つけることができず、それらのどれもユースケースに適合しませんでした。

問題が何であるかがわかります-マングース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 != [])ブロックで非同期保存を実行してから、resolveコールバックでinvited配列を変更しています。 nRoom.invited.push()呼び出しは、常に2番目のsave()呼び出しの後に発生します。 また、JSでは[] == []が偽であるため、 req.body.invite != []は常に真になります。

@ 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 } }を使用しても違いはありますか?

$ setdocsは文字列のみを表示します

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)にアップグレードしましたが、まだ問題があります。

以下のスクリプトは、Mongoose5.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 評価