Wie kann ich mehrere Dokumente gleichzeitig in Mongoose / Node.js speichern?

75

Im Moment benutze ich save, um ein einzelnes Dokument hinzuzufügen. Angenommen, ich habe eine Reihe von Dokumenten, die ich als einzelne Objekte speichern möchte. Gibt es eine Möglichkeit, sie alle mit einem einzigen Funktionsaufruf hinzuzufügen und dann einen einzigen Rückruf zu erhalten, wenn dies erledigt ist? Ich könnte alle Dokumente einzeln hinzufügen, aber die Verwaltung der Rückrufe, um herauszufinden, wann alles erledigt ist, wäre problematisch.

Hoa
quelle
Sie müssen den Codefluss mithilfe einer asynchronen Bibliothek wie async steuern. (Es gibt parallele Funktion und wenn abgeschlossen, wird der Rückruf aufgerufen)
Risto Novik

Antworten:

38

Mongoose hat noch keine Bulk-Inserts implementiert (siehe Problem Nr. 723 ).

Da Sie die Anzahl der Dokumente kennen, die Sie speichern, können Sie Folgendes schreiben:

var total = docArray.length
  , result = []
;

function saveAll(){
  var doc = docArray.pop();

  doc.save(function(err, saved){
    if (err) throw err;//handle error

    result.push(saved[0]);

    if (--total) saveAll();
    else // all saved here
  })
}

saveAll();

Dies ist natürlich eine Stop-Gap-Lösung, und ich würde empfehlen, eine Art Flusskontrollbibliothek zu verwenden (ich verwende q und es ist fantastisch).

diversario
quelle
2
Können Sie die Lösung bitte mit q bereitstellen?
Manu
5
Ich denke nicht, dass dies "gleichzeitig" ist. Jeder Speichervorgang wird erst aufgerufen, wenn der vorherige abgeschlossen ist.
Ted Bigham
Wahr. Ein gleichzeitigerer Ansatz wäre beispielsweise, alle saves auszulösen, darauf zu warten, dass alle ihren Rückruf aufrufen und eine Reihe von Ergebnissen zurückgeben. Sie könnten dafür Async oder eine vielversprechende Oberfläche verwenden.
Diversario
Wann wird diese Bedingung if (--total)falsch sein?
Gobliins
1
Ich denke, die obige Antwort ist sehr alt. In Mungo gibt es eine Methode namens insertMany (). Prüfen mongoosejs.com/docs/api.html#model_Model.insertMany
Epsi95
92

Mongoose unterstützt jetzt die Übergabe mehrerer Dokumentstrukturen an Model.create . Um das API-Beispiel zu zitieren, wird die Übergabe eines Arrays oder einer varargs-Liste von Objekten mit einem Rückruf am Ende unterstützt:

Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) {
    if (err) // ...
});

Oder

var array = [{ type: 'jelly bean' }, { type: 'snickers' }];
Candy.create(array, function (err, jellybean, snickers) {
    if (err) // ...
});

Bearbeiten: Wie viele angemerkt haben, führt dies keine echte Masseneinfügung durch - es verbirgt einfach die Komplexität, saveselbst mehrmals anzurufen. Im Folgenden finden Sie Antworten und Kommentare, in denen erläutert wird, wie Sie den tatsächlichen Mongo-Treiber verwenden, um im Interesse der Leistung eine Masseneinfügung zu erzielen.

Pascal Zajac
quelle
13
Hinweis: Dies ist keine BULK-Einfügung. Die zugrunde liegende Mungo-Implementierung durchläuft alle Elemente und schreibt sie einzeln fest.
außerhalb2344
1
^ Das ist sehr relevant, da es die Leistung für diejenigen, die es intensiv nutzen, ernsthaft beeinträchtigen kann.
Lino Silva
1
Antwort für Aaron Heckman 2011: nicht wirklich. Model.create (doc1 [, docN], Rückruf) hilft hier irgendwie, aber es ruft immer noch model.save für Sie auf. Wenn Sie mit "schneller" "Alle Mungo-Hooks und Validierung umgehen" meinen, können Sie zum nativen Treiber wechseln und ihn direkt verwenden: Movie.collection.insert (Dokumente, Optionen, Rückruf) github.com/christkv/node-mongodb -native / blob / master / lib / mongodb /…
arcseldon
In Anbetracht aller Kommentare und neuen Antworten zu diesem Thema habe ich meine geändert, um auf die Einwände in Bezug auf Masseneinfügung und Leistung hinzuweisen. Ich bin jedoch gezwungen zu bemerken, dass Hoa die Leistung nie als Grund für diese Frage genannt hat - sie wollten einfach vermeiden, auf den Erfolg mehrerer Rückrufe zu warten.
Pascal Zajac
1
Ich möchte betonen, dass dies NICHT der beste Weg ist, Masseneinfügungen vorzunehmen, wenn Sie mit einer großen Menge von Dokumenten arbeiten . Siehe stackoverflow.com/a/24848148/778272, das eine bessere Erklärung enthält.
Lucio Paiva
57

Mongoose 4.4 hat eine Methode namens hinzugefügt insertMany

Verknüpfung zum Überprüfen eines Arrays von Dokumenten und Einfügen in MongoDB, wenn alle gültig sind. Diese Funktion ist schneller als .create (), da nur eine Operation an den Server gesendet wird und nicht eine für jedes Dokument.

Zitiert vkarpov15 aus Ausgabe # 723 :

Die Nachteile sind, dass insertMany () keine Pre-Save-Hooks auslöst, aber eine bessere Leistung erzielen sollte, da nur 1 Roundtrip zur Datenbank statt 1 für jedes Dokument durchgeführt wird.

Die Signatur der Methode ist identisch mit create:

Model.insertMany([ ... ], (err, docs) => {
  ...
})

Oder mit Versprechungen:

Model.insertMany([ ... ]).then((docs) => {
  ...
}).catch((err) => {
  ...
})
Pier-Luc Gendreau
quelle
Danke dafür. Es heißt, es werden sie eingefügt, wenn sie alle gültig sind. Bedeutet dies, wenn man versagt, werden alle scheitern?
Aron
Es ist eine Massenoperation, aber nicht atomar. Ich bin mir nicht sicher, wie Mongoose das macht und kann es momentan nicht testen, aber es sollte die Anzahl der erfolgreichen Schreibvorgänge zurückgeben. Weitere Details finden Sie in der Dokumentation von MongoDB: docs.mongodb.com/manual/reference/method/…
Pier-Luc Gendreau
4
Wenn eine Einfügung fehlschlägtMany fügt nichts ein, ich habe die Tests gemacht
Shontauro
In Situationen, in denen Sie doppelte Dokumente validieren müssen, kann das Einfügen vieler Dokumente schmerzhaft sein. es scheint gut zu funktionieren, wenn die _id bereits in neuen Dokumenten angegeben ist; es wirft einen doppelten Fehler für diese
zinoadidi
26

Masseneinfügungen in Mongoose können mit .insert () durchgeführt werden, es sei denn, Sie müssen auf Middleware zugreifen.

Model.collection.insert(docs, options, callback)

https://github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/collection.js#L71-91

Cyberwombat
quelle
1
Antwort für Aaron Heckman 2011: nicht wirklich. Model.create (doc1 [, docN], Rückruf) hilft hier irgendwie, aber es ruft immer noch model.save für Sie auf. Wenn Sie mit "schneller" "Alle Mungo-Hooks und Validierung umgehen" meinen, können Sie zum nativen Treiber wechseln und ihn direkt verwenden: Movie.collection.insert (Dokumente, Optionen, Rückruf) github.com/christkv/node-mongodb -native / blob / master / lib / mongodb /…
arcseldon
1
Ich sehe diese Antwort immer wieder, aber dies ist nicht wirklich eine "Mungo" -Methode. Dies umgeht die Mungomodelle vollständig. Wenn Sie für einige Felder in den Mungomodellen Standardwerte eingerichtet haben, werden diese ignoriert und nicht in die Datenbank eingefügt.
Nahn
1
Wie verwende ich Model.collection.insertin Mungo? Bitte geben Sie ein Beispiel.
Steve K
1
Ich weiß, dass es einige Kritiker zu diesem Ansatz gibt, aber dies ist tatsächlich die beste (wenn nicht die einzige ) Antwort, wenn Sie mit einer großen Menge von Dokumenten arbeiten . Diese andere Antwort (http://stackoverflow.com/a/24848148/778272) erklärt, warum es besser ist und gibt ein Beispiel.
Lucio Paiva
Kann mir jemand vorschlagen, welche Möglichkeiten es gibt?
Noushad
13

Verwenden Sie asynchron parallel und Ihr Code sieht folgendermaßen aus:

  async.parallel([obj1.save, obj2.save, obj3.save], callback);

Da die Konvention in Mongoose dieselbe ist wie in asynchron (err, callback), müssen Sie sie nicht in Ihre eigenen Rückrufe einschließen. Fügen Sie einfach Ihre gespeicherten Aufrufe in ein Array ein, und Sie erhalten einen Rückruf, wenn alles abgeschlossen ist.

Wenn Sie mapLimit verwenden, können Sie steuern, wie viele Dokumente Sie parallel speichern möchten. In diesem Beispiel speichern wir 10 Dokumente parallel, bis alle Elemente erfolgreich gespeichert wurden.

async.mapLimit(myArray, 10, function(document, next){
  document.save(next);
}, done);
Christian Landgren
quelle
2
Interessant - würde es Ihnen etwas ausmachen, ein realistisches Beispiel mit einem zu geben myArray? Während myArray 10 Millionen Artikel hat.
Steve K
8

Ich weiß, dass dies eine alte Frage ist, aber es macht mir Sorgen, dass es hier keine richtig richtigen Antworten gibt. Bei den meisten Antworten geht es nur darum, alle Dokumente zu durchlaufen und jedes einzeln zu speichern. Dies ist eine schlechte Idee, wenn Sie mehr als ein paar Dokumente haben, und der Vorgang wird sogar für eine von vielen Anfragen wiederholt.

MongoDB hat speziell einen batchInsert()Aufruf zum Einfügen mehrerer Dokumente, und dieser sollte vom nativen Mongodb-Treiber verwendet werden. Mongoose basiert auf diesem Treiber und unterstützt keine Batch-Einfügungen. Dies ist wahrscheinlich sinnvoll, da es sich um ein Objektdokumentmodellierungswerkzeug für MongoDB handeln soll.

Lösung: Mongoose wird mit dem nativen MongoDB-Treiber geliefert. Sie können diesen Treiber verwenden, indem Sie ihn benötigen require('mongoose/node_modules/mongodb')(nicht sicher, aber Sie können den mongodb npm immer wieder installieren, wenn er nicht funktioniert, aber ich denke, er sollte es tun) und dann einen ordnungsgemäßen Vorgang ausführenbatchInsert

Munim
quelle
2
Falsch, Pascals Antwort geht völlig daneben. Menschen, die Masseneinlagen benötigen, benötigen diese in der Regel, weil sie 10.000.000 Artikel auf einmal einfügen möchten. Ohne Masseneinsatz kann ein Vorgang, der einige Sekunden dauern sollte, Stunden dauern. Model.create ist ein epischer Fehler, da es sich als Masseneinsatz ausgibt, aber unter der Haube ist es nur eine for-Schleife.
user3690202
Mungo braucht dann dringend eine Überarbeitung. Auch ihre Dokumente lassen VIEL zu wünschen übrig.
Steve K
Ich denke, @ Yashuas Frage spricht es mit dem zugrunde liegenden mongodbJavascript-Treiber an.
Ehtesh Choudhury
8

Neuere Versionen von MongoDB unterstützen Massenoperationen:

var col = db.collection('people');
var batch = col.initializeUnorderedBulkOp();

batch.insert({name: "John"});
batch.insert({name: "Jane"});
batch.insert({name: "Jason"});
batch.insert({name: "Joanne"});

batch.execute(function(err, result) {
    if (err) console.error(err);
    console.log('Inserted ' + result.nInserted + ' row(s).');
}
inxilpro
quelle
5

Hier ist ein anderer Weg ohne zusätzliche Bibliotheken (keine Fehlerprüfung enthalten)

function saveAll( callback ){
  var count = 0;
  docs.forEach(function(doc){
      doc.save(function(err){
          count++;
          if( count == docs.length ){
             callback();
          }
      });
  });
}
mindandmedia
quelle
3

Sie können das von Mungo zurückgegebene Versprechen verwenden save, Promisein Mungo gibt es nicht alle, aber Sie können die Funktion mit diesem Modul hinzufügen.

Erstellen Sie ein Modul, das das Versprechen von Mungos bei allen verbessert.

var Promise = require("mongoose").Promise;

Promise.all = function(promises) {
  var mainPromise = new Promise();
  if (promises.length == 0) {
    mainPromise.resolve(null, promises);
  }

  var pending = 0;
  promises.forEach(function(p, i) {
    pending++;
    p.then(function(val) {
      promises[i] = val;
      if (--pending === 0) {
        mainPromise.resolve(null, promises);
      }
    }, function(err) {
      mainPromise.reject(err);
    });
  });

  return mainPromise;
}

module.exports = Promise;

Dann verwenden Sie es mit Mungo:

var Promise = require('./promise')

...

var tasks = [];

for (var i=0; i < docs.length; i++) {
  tasks.push(docs[i].save());
}

Promise.all(tasks)
  .then(function(results) {
    console.log(results);
  }, function (err) {
    console.log(err);
  })
Kristian Benoit
quelle
Nicht gefangener TypeError: Promise Resolver undefined ist keine Funktion in Promise.js
krishan kumar
1

Verwenden Sie die insertManyFunktion, um viele Dokumente einzufügen. Dies sendet nur eine Operation an den Server und Mongooseüberprüft alle Dokumente, bevor der Mongo-Server erreicht wird. Standardmäßig wird das MongooseElement in der Reihenfolge eingefügt, in der es im Array vorhanden ist. Wenn Sie keine Reihenfolge einhalten können, stellen Sie ein ordered:false.

Wichtig - Fehlerbehandlung:

Wenn die ordered:trueValidierung und Fehlerbehandlung in einer Gruppe erfolgt, schlägt alles fehl, wenn eine fehlschlägt.

Wenn die ordered:falseValidierung und Fehlerbehandlung einzeln erfolgt und der Betrieb fortgesetzt wird. Fehler werden in einer Reihe von Fehlern zurückgemeldet.

Praveena
quelle
0

Fügen Sie eine Datei mit dem Namen mongoHelper.js hinzu

var MongoClient = require('mongodb').MongoClient;

MongoClient.saveAny = function(data, collection, callback)
{
    if(data instanceof Array)
    {
        saveRecords(data,collection, callback);
    }
    else
    {
        saveRecord(data,collection, callback);
    }
}

function saveRecord(data, collection, callback)
{
    collection.save
    (
        data,
        {w:1},
        function(err, result)
        {
            if(err)
                throw new Error(err);
            callback(result);
        }
    );
}
function saveRecords(data, collection, callback)
{
    save
    (
        data, 
        collection,
        callback
    );
}
function save(data, collection, callback)
{
    collection.save
    (
        data.pop(),
        {w:1},
        function(err, result)
        {
            if(err)
            {               
                throw new Error(err);
            }
            if(data.length > 0)
                save(data, collection, callback);
            else
                callback(result);
        }
    );
}

module.exports = MongoClient;

Dann müssen Sie in Ihrem Code ändern

var MongoClient = require("./mongoHelper.js");

Wenn es dann Zeit ist, den Anruf zu speichern (nachdem Sie die Sammlung verbunden und abgerufen haben)

MongoClient.saveAny(data, collection, function(){db.close();});

Sie können die Fehlerbehandlung an Ihre Bedürfnisse anpassen, den Fehler im Rückruf zurückgeben usw.

Eulalie367
quelle
0

Dies ist eine alte Frage, die jedoch in den Google-Ergebnissen bei der Suche nach "Mungo-Einfügungsarray von Dokumenten" zuerst für mich auftauchte.

Es gibt zwei Optionen model.create () [mongoose] und model.collection.insert () [mongodb], die Sie verwenden können. Sehen Sie sich hier eine gründlichere Diskussion der Vor- und Nachteile der einzelnen Optionen an:

Mungo (Mongodb) Batch-Einsatz?

vbuser2004
quelle
0

Hier ist ein Beispiel für die Verwendung von MongoDBs Model.collection.insert()direkt in Mongoose. Bitte beachten Sie, dass Sie, wenn Sie nicht so viele Dokumente haben, beispielsweise weniger als 100 Dokumente, den Massenvorgang von MongoDB nicht verwenden müssen ( siehe hier ).

MongoDB unterstützt auch das Masseneinfügen, indem ein Array von Dokumenten an die Methode db.collection.insert () übergeben wird.

var mongoose = require('mongoose');

var userSchema = mongoose.Schema({
  email : { type: String, index: { unique: true } },
  name  : String  
}); 

var User = mongoose.model('User', userSchema);


function saveUsers(users) {
  User.collection.insert(users, function callback(error, insertedDocs) {
    // Here I use KrisKowal's Q (https://github.com/kriskowal/q) to return a promise, 
    // so that the caller of this function can act upon its success or failure
    if (!error)
      return Q.resolve(insertedDocs);
    else
      return Q.reject({ error: error });
  });
}

var users = [{email: '[email protected]', name: 'foo'}, {email: '[email protected]', name: 'baz'}];
saveUsers(users).then(function() {
  // handle success case here
})
.fail(function(error) {
  // handle error case here
});
Javad Sadeqzadeh
quelle
Welche Mungo-Version ist das?
R01010010