Mongoose: Model.save() n'enregistre pas les tableaux intégrés

Créé le 10 nov. 2012  ·  26Commentaires  ·  Source: Automattic/mongoose

J'ai des tableaux de documents imbriqués, et lorsque je les modifie et que je fais un .save () sur le modèle, il n'enregistre pas les modifications dans le document imbriqué.

Voici mes modèles et schémas (certains omis par souci de concision) :

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

Modèle avant event.save() appelé :

{ 
     __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: [] 
}

Le même objet avec les votes imbriqués juste avant que event.save() soit appelé.

{
   "__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":[]
}

Lorsque event.save() est appelé, aucune erreur n'est renvoyée, mais le schéma de votes imbriqués à l'intérieur du schéma d'événements imbriqués n'est pas réellement enregistré. Si j'utilise la même logique globale dans l'objet événement de niveau supérieur pour enregistrer un vote, cela fonctionne.

Lorsque j'ai brièvement parcouru le code, il semble que .save() soit censé être un raccourci pour enregistrer de nouveaux objets, ainsi que pour mettre à jour ceux qui existent déjà.

Mon intuition est que Model.prototype._delta n'est pas assez profond pour attraper tous les objets imbriqués, https://github.com/LearnBoost/mongoose/blob/master/lib/model.js#L529

Commentaire le plus utile

Je vois quel est le problème - cas classique de la première question sur la FAQ de la mangouste.

Merci, c'était ce problème dans mon cas ! :)

model.myArray[index] = anyValue

devient

model.myArray.set(index, anyValue)

Tous les 26 commentaires

Veuillez également inclure le code que vous utilisez pour reproduire le problème (où vous manipulez le document avant de l'enregistrer, etc.)

Bien sûr, je l'ai posté dans un résumé pour éviter un mur de code ici, https://gist.github.com/4055392

J'ai inclus à la fois l'enregistrement d'un vote dans l'événement de niveau supérieur (fonctionnant) ainsi que l'enregistrement dans un événement imbriqué (ne fonctionnant pas). Comme vous pouvez le voir, ils ajoutent ou mettent à jour un vote, puis l'enregistrent de la même manière.

Essayez de corriger votre référence EventSchema utilisée dans 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
})


fermeture, pas de réponse. merci de rouvrir si besoin.

J'ai le même problème où .save() renvoie un document qui a mis à jour l'objet incorporé mais il n'est pas réellement enregistré dans mongodb. S'il vous plaît aidez-moi à ce sujet.

@ Manojkumar91 pouvez-vous fournir un code reproduisant cela ? Serait très utile :)

J'ai rencontré ça aussi, voici mon modèle :

/*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);

Cela fait partie d'une application beaucoup plus vaste, donc je n'inclus que le filtre d'action du contrôleur pertinent, mais je peux parler du reste dans un sens plus large. Ci-dessous le code du filtre :

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

En effet, avant d'exécuter ce filtre, un filtre est appelé qui crée une structure de formulaire dépendante de l'état basée sur le tableau bidimensionnel de mesures et de tailles, puis cette liste d'éléments est analysée pour fournir des mises à jour. J'ai testé avec des données factices et le graphique factice actuel (tiré de la console) ressemble à ceci :

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

J'ai testé le tableau des tailles modifié juste avant l'enregistrement, étant donné une entrée de "1111" dans l'emplacement tailles[0].lengths[0] et cela apparaît dans le document lorsqu'il est inspecté dans Node, mais l'enregistrement revient exactement ce même document que la pré-sauvegarde.

S'il vous plaît laissez-moi savoir si vous avez besoin de détails supplémentaires.

@crispen-smith Je vais avoir besoin de quelques éclaircissements, il y a un peu trop de code pour que je puisse le comprendre facilement. Pouvez-vous activer le mode de débogage de mangouste avec require('mongoose').set('debug', true); et publier le résultat ? Cela me montre les requêtes et les écritures qui sont envoyées au serveur.

Oui, c'est beaucoup de code, mais en même temps... pas assez pour l'expliquer facilement.

Ainsi, l'exécution du débogage semble suggérer que la sauvegarde n'est même pas touchée, voici une sauvegarde avec un changement de nom :

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

Voici l'exécution de la même séquence d'opérations avec une modification d'un nom et d'une valeur du tableau de chaînes :

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

Et, avec seulement la modification d'une variable à l'intérieur du tableau :

Mangouste : charts.findOne({ nom : 'Graphique avec mesures' }) { champs : undefined }
(Oui, c'est tout... pas de requête de mise à jour)

Je vais essayer de le patcher en supprimant l'intégralité du sous-doc à chaque fois, du moins pour le moment... Je n'ai pas besoin d'intégrité référentielle ou des identifiants d'objet pour cet usage particulier.

--METTRE À JOUR--
J'ai essayé de supprimer et de recharger, mais cela ne semble pas fonctionner non plus. Bien que je n'aie pas de trace de sortie pour cette pièce, il semble que la suppression ne déclenche pas non plus d'opération de back-end. Y a-t-il une sorte de vérification sale qui n'est pas déclenchée ?

Une option pour rouvrir cela compte tenu de mes notes ci-dessus ?

@crispen-smith voici ma tentative de base pour reproduire le problème en tant que script autonome :

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

Pas de dé pour l'instant, ça marche comme prévu. Pouvez-vous modifier l'exemple ci-dessus pour illustrer le problème que vous rencontrez ? Je n'ai pas réussi à traduire vos descriptions en prose en code.

Bien sûr, je regarderai ça un peu plus tard ce soir.

Essayez-le ?

/*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);

    });
  });
});

Voici ma sortie de console dans ma console de nœud :

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()) lancer la raison ;
^
Erreur d'assertion : "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)
au modèle.(/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/document.js:1569:39)
at 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)

(désolé, j'ai eu beaucoup de mal à mettre cela dans une bonne démarque, c'est le mieux que j'ai pu faire.)
Et voici la sortie mongoDB :

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

Du point de vue CRUD, l'essence de celui-ci est qu'il n'y a pas de problème avec la création ou les lectures, les mises à jour de mise échouent.

TLDR ;
Dans mon cas d'utilisation, cela signifie qu'une fois qu'un ensemble de longueurs a été défini pour une mesure, ils ne peuvent pas être modifiés. Je travaille sur un projet de vente au détail de mode avec deux cas d'utilisation qui nécessiteront des tableaux au sein de sous-docs. Le premier cas d'utilisation est que les utilisateurs de vanilla auront leur propre profil et devraient pouvoir le maintenir. Le deuxième cas d'utilisation est que le rôle d'administrateur peut créer des graphiques personnalisés pour les listes de différents types de produits (hauts, bas, etc.). Je ne m'attendrais normalement pas à ce que le profil d'administrateur ait besoin de modifier ces graphiques secondaires, mais ce serait toujours agréable de les avoir pour ce cas d'utilisation.

En théorie, la fonctionnalité actuelle présente en fait un bel objet immuable (accidentel), mais la quantité de travail nécessaire pour l'utiliser comme immuable plutôt que simplement pour l'éditer n'est pas triviale.

Je vois quel est le problème - cas classique de la première question sur la FAQ de la mangouste . Mongoose ne peut pas suivre les modifications lorsque vous définissez un index de tableau directement sans quelque chose comme les proxy ES6 ou ES7 Object.observe() . Utilisation

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

ou

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

D'accord, ça a du sens. Étrangement, (et cela peut être attribué à mes stratégies de recherche), je n'ai pu trouver que des fonctions qui fonctionnaient 1x par sauvegarde, et aucune de celles-ci n'aurait correspondu au cas d'utilisation.

Je vois quel est le problème - cas classique de la première question sur la FAQ de la mangouste.

Merci, c'était ce problème dans mon cas ! :)

model.myArray[index] = anyValue

devient

model.myArray.set(index, anyValue)

C'était aussi un problème pour moi, j'aurais aimé voir ce fil plus tôt!

Si vous n'êtes pas sûr que mongoose soit informé des changements, vous pouvez utiliser

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

J'ai également résolu mon problème avec Array.set().
:+1:

J'ai la même erreur, c'est mon extrait de code
image
Et c'est la bûche de la mangouste
image
C'est ce que je reçois du facteur
image

Quelque chose ne va pas ?

++ MODIFIER ++

  1. J'ai essayé d'utiliser Array.set, obtenez le même résultat

Je pense que la meilleure question est de savoir s'il y a quelque chose de bien. Vous effectuez une sauvegarde asynchrone dans le bloc if (req.body.invite != []) , puis modifiez le tableau invited dans le rappel de résolution. L'appel nRoom.invited.push() se produira toujours après le 2e appel save(). De plus, req.body.invite != [] sera toujours vrai car [] == [] est faux en JS.

@vkarpov15 Haha Ok, merci pour la réponse ! 👍 J'ai plein de choses à apprendre :)

@peterkrieg Merci !

Bonjour, j'ai ce problème lors de la mise à jour de documents profondément intégrés, quelqu'un a-t-il un conseil ? Mon code est à :

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

@thehme quelle version de mangouste utilisez-vous ?

sur cette ligne, {$set: {["dT." + index +".ts." + i + ".th"]: newValue}} , les crochets me semblent déplacés. cela fait-il une différence d'utiliser {$set: { "dT." + index +".ts." + i + ".th": newValue } } place ?

$set docs n'affichent qu'une chaîne

n'hésitez pas à nous rejoindre sur Gitter.im ou Slack pour en parler en temps réel

J'utilise 5.5.11 et j'ai toujours ce problème. J'ai essayé toutes les solutions proposées sur ce fil sans succès. La mise en page de mon document ressemble à ceci :

{
  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

Edit : mis à niveau vers la dernière version (5.7.6) et le problème persiste.

Le script ci-dessous fonctionne correctement sur Mongoose 5.7.6. Veuillez ouvrir un nouveau numéro et suivre le modèle de problème.

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);
}
Cette page vous a été utile?
0 / 5 - 0 notes