Come posso usare un cursor.forEach () in MongoDB usando Node.js?

Ho una vasta collezione di documenti nel mio DB e mi chiedo come posso scorrere tutti i documenti e aggiornarli, ogni documento con un valore diverso.

La risposta di Leonid è ottima, ma voglio rafforzare l’importanza dell’uso di asincrone / promesse e di dare una soluzione diversa con un esempio di promesse.

La soluzione più semplice a questo problema è quella di eseguire il ciclo di documenti Any e chiamare un aggiornamento. Di solito, non è necessario chiudere la connessione db dopo ogni richiesta , ma se è necessario chiudere la connessione, fare attenzione. Devi solo chiuderlo se sei sicuro che tutti gli aggiornamenti hanno completato l’esecuzione.

Un errore comune è quello di chiamare db.close() dopo che tutti gli aggiornamenti sono stati inviati senza sapere se sono stati completati. Se lo fai, riceverai errori.

Implementazione errata :

 collection.find(query).each(function(err, doc) { if (err) throw err; if (doc) { collection.update(query, update, function(err, updated) { // handle }); } else { db.close(); // if there is any pending update, it will throw an error there } }); 

Tuttavia, dato che db.close() è anche un’operazione asincrona (la sua firma ha un’opzione di callback) potresti essere fortunato e questo codice può finire senza errori. Può funzionare solo quando è necessario aggiornare solo pochi documenti in una piccola raccolta (quindi, non provare).

Soluzione corretta:

Siccome una soluzione con async è stata già proposta da Leonid , qui di seguito segue una soluzione che usa le promesse di Q.

 var Q = require('q'); var client = require('mongodb').MongoClient; var url = 'mongodb://localhost:27017/test'; client.connect(url, function(err, db) { if (err) throw err; var promises = []; var query = {}; // select all docs var collection = db.collection('demo'); var cursor = collection.find(query); // read all docs cursor.each(function(err, doc) { if (err) throw err; if (doc) { // create a promise to update the doc var query = doc; var update = { $set: {hi: 'there'} }; var promise = Q.npost(collection, 'update', [query, update]) .then(function(updated){ console.log('Updated: ' + updated); }); promises.push(promise); } else { // close the connection after executing all promises Q.all(promises) .then(function() { if (cursor.isClosed()) { console.log('all items have been processed'); db.close(); } }) .fail(console.error); } }); }); 
var MongoClient = require('mongodb').MongoClient, assert = require('assert'); MongoClient.connect('mongodb://localhost:27017/crunchbase', function(err, db) { assert.equal(err, null); console.log("Successfully connected to MongoDB."); var query = { "category_code": "biotech" }; db.collection('companies').find(query).toArray(function(err, docs) { assert.equal(err, null); assert.notEqual(docs.length, 0); docs.forEach(function(doc) { console.log(doc.name + " is a " + doc.category_code + " company."); }); db.close(); }); });
var MongoClient = require('mongodb').MongoClient, assert = require('assert'); MongoClient.connect('mongodb://localhost:27017/crunchbase', function(err, db) { assert.equal(err, null); console.log("Successfully connected to MongoDB."); var query = { "category_code": "biotech" }; db.collection('companies').find(query).toArray(function(err, docs) { assert.equal(err, null); assert.notEqual(docs.length, 0); docs.forEach(function(doc) { console.log(doc.name + " is a " + doc.category_code + " company."); }); db.close(); }); }); 

Si noti che la chiamata .toArray sta facendo in modo che l’applicazione .toArray l’intero set di dati.

var MongoClient = require('mongodb').MongoClient, assert = require('assert'); MongoClient.connect('mongodb://localhost:27017/crunchbase', function(err, db) { assert.equal(err, null); console.log("Successfully connected to MongoDB."); var query = {"category_code": "biotech"}; var cursor = db.collection('companies').find(query); function(doc) { cursor.forEach( console.log( doc.name + " is a " + doc.category_code + " company." ); }, function(err) { assert.equal(err, null); return db.close(); } ); });
var MongoClient = require('mongodb').MongoClient, assert = require('assert'); MongoClient.connect('mongodb://localhost:27017/crunchbase', function(err, db) { assert.equal(err, null); console.log("Successfully connected to MongoDB."); var query = {"category_code": "biotech"}; var cursor = db.collection('companies').find(query); function(doc) { cursor.forEach( console.log( doc.name + " is a " + doc.category_code + " company." ); }, function(err) { assert.equal(err, null); return db.close(); } ); }); 

Si noti che il cursore restituito da find() è assegnato al var cursor . Con questo approccio, invece di recuperare tutti i dati in memoria e consumare dati contemporaneamente, stiamo trasmettendo i dati alla nostra applicazione. find() può creare un cursore immediatamente perché in realtà non fa una richiesta al database finché non proviamo a utilizzare alcuni dei documenti che fornirà. Il punto del cursor è descrivere la nostra query. Il secondo parametro su cursor.forEach mostra cosa fare quando il driver si scarica o si verifica un errore.

Nella versione iniziale del codice precedente, era toArray() che forzava la chiamata al database. Significava che avevamo bisogno di TUTTI i documenti e volevamo che fossero in un array .

Inoltre, MongoDB restituisce i dati in formato batch. L’immagine sotto mostra le richieste dai cursori (dall’applicazione) a MongoDB

Richieste di cursori MongoDB

forEach è meglio di toArray perché possiamo elaborare i documenti man mano che arrivano fino alla fine. Contrasto con toArray – dove aspettiamo TUTTI i documenti da recuperare e l’ intero array è costruito. Ciò significa che non stiamo ottenendo alcun vantaggio dal fatto che il driver e il sistema di database stanno lavorando insieme ai risultati batch per l’applicazione. Il batch è pensato per fornire efficienza in termini di overhead della memoria e il tempo di esecuzione. Approfittane, se puoi nella tua applicazione .

Ed ecco un esempio di utilizzo di un cursore Mongoose async con promesse:

 new Promise(function (resolve, reject) { collection.find(query).cursor() .on('data', function(doc) { // ... }) .on('error', reject) .on('end', resolve); }) .then(function () { // ... }); 

Riferimento:

  • Cursori di Mongoose
  • Stream e promesse

Il node-mongodb-native ora supporta un parametro endCallback su cursor.forEach come per gestire l’evento DOPO l’intera iterazione, fare riferimento al documento ufficiale per i dettagli http://mongodb.github.io/node-mongodb-native/ 2.2 / api / Cursor.html # forEach .

Si noti inoltre che .each è deprecato nel driver nativo di nodejs ora.