web-dev-qa-db-ja.com

Mongoose / Node.jsで複数のドキュメントを同時に保存するにはどうすればよいですか?

現時点では、保存を使用して単一のドキュメントを追加します。単一のオブジェクトとして保存したいドキュメントの配列があるとします。単一の関数呼び出しですべてを追加し、完了時に単一のコールバックを取得する方法はありますか?すべてのドキュメントを個別に追加することもできますが、コールバックを管理してすべてが完了したときに解決するのは問題です。

66
Hoa

Mongooseには一括挿入がまだ実装されていません( issue#72 を参照)。

保存しているドキュメントの数がわかっているので、次のように書くことができます。

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

もちろん、これはストップギャップソリューションであり、何らかのフロー制御ライブラリを使用することをお勧めします(私は q を使用していますが、それは素晴らしいです)。

36
diversario

Mongooseは、複数のドキュメント構造を Model.create に渡すことをサポートするようになりました。 APIの例を引用すると、最後にコールバックを持つ配列またはオブジェクトの可変引数リストのいずれかが渡されます。

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

または

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

Edit:多くの人が指摘したように、これは真のバルク挿入を実行しません-saveを複数回呼び出す複雑さを単純に隠します。以下に、パフォーマンスのために実際のMongoドライバーを使用して一括挿入を行う方法を説明する回答とコメントがあります。

86
Pascal Zajac

Mongoose 4.4は insertMany というメソッドを追加しました

ドキュメントの配列を検証し、それらがすべて有効な場合にMongoDBに挿入するためのショートカット。この関数は、各ドキュメントに対して1つではなく1つの操作のみをサーバーに送信するため、.create()よりも高速です。

問題からvkarpov15を引用 #72

トレードオフは、insertMany()が事前保存フックをトリガーしないことですが、各ドキュメントに対して1回ではなくデータベースへの往復が1回だけなので、パフォーマンスが向上するはずです。

メソッドのシグネチャはcreateと同じです:

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

または、約束をもって:

Model.insertMany([ ... ]).then((docs) => {
  ...
}).catch((err) => {
  ...
})
44

ミドルウェアにアクセスする必要がない限り、Mongooseでの一括挿入は.insert()で実行できます。

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

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

25
cyberwombat

async parallel を使用すると、コードは次のようになります。

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

Mongooseの規則はasync(err、callback)と同じなので、独自のコールバックでラップする必要はありません。保存呼び出しを配列に追加するだけで、すべてが完了するとコールバックが取得されます。

MapLimitを使用すると、並行して保存するドキュメントの数を制御できます。この例では、すべてのアイテムが正常に保存されるまで、10個のドキュメントを並行して保存します。

async.mapLimit(myArray, 10, function(document, next){
  document.save(next);
}, done);
12

これは古い質問であることは知っていますが、ここには適切な正解がないことが心配です。ほとんどの答えは、すべてのドキュメントを繰り返し処理し、それぞれを個別に保存することについて話しているだけです。これは、複数のドキュメントがある場合は悪い考えであり、プロセスは多くのリクエストの1つでも繰り返されます。

MongoDBには特に、複数のドキュメントを挿入するためのbatchInsert()呼び出しがあり、これはネイティブmongodbドライバーから使用する必要があります。 Mongooseはこのドライバーに基づいて構築されており、バッチ挿入をサポートしていません。 MongoDBのオブジェクトドキュメントモデリングツールになるはずなので、おそらく理にかなっています。

解決策:MongooseにはネイティブMongoDBドライバーが付属しています。 require('mongoose/node_modules/mongodb')を要求することでそのドライバーを使用できます(これについてはあまりよくわかりませんが、もし機能しない場合はいつでもmongodb npmをインストールできますが、そうすべきだと思います)、適切なbatchInsert

8
Munim

MongoDBの新しいバージョンは、一括操作をサポートしています。

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).');
}
7
inxilpro

追加のライブラリを使用しない別の方法を次に示します(エラーチェックは含まれません)

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

Mongooseで返されるpromiseを使用できますsave、mongooseのPromiseにはすべてはありませんが、このモジュールで機能を追加できます。

すべてのマングースの約束を強化するモジュールを作成します。

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;

次に、それをmongooseで使用します。

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);
  })
2
Kristian Benoit

insertMany関数を使用して、多くのドキュメントを挿入します。これにより、1つの操作のみがサーバーに送信され、Mongooseはmongoサーバーにアクセスする前にすべてのドキュメントを検証します。デフォルトでは、Mongooseは配列に存在する順序でアイテムを挿入します。順序を維持しなくても問題ない場合は、ordered:falseを設定します。

重要-エラー処理:

グループ内でordered:true検証とエラー処理が発生すると、1つが失敗するとすべてが失敗します。

ordered:false検証とエラー処理が個別に行われ、操作が継続される場合。エラーはエラーの配列で報告されます。

1
Praveena

MongoHelper.jsというファイルを追加します

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;

次に、コードの変更で次のことを行う必要があります

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

次に、コールを保存する時間になったら(コレクションを接続して取得した後)

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

必要に応じてエラー処理を変更したり、コールバックなどでエラーを返したりできます。

0
Eulalie367

これは古い質問ですが、「mongoose insert array of documents」を検索すると、Googleの結果で最初に出てきました。

Model.create()[mongoose]とmodel.collection.insert()[mongodb]の2つのオプションがあり、使用できます。各オプションの賛否両論についての詳細な議論はこちらをご覧ください。

マングース(mongodb)バッチ挿入?

0
vbuser2004

MongooseでMongoDBのModel.collection.insert()を直接使用する例を次に示します。ドキュメントがそれほど多くない場合、たとえば100未満のドキュメントがある場合は、MongoDBの一括操作を使用する必要はありません( こちらを参照 )。

MongoDBは、ドキュメントの配列をdb.collection.insert()メソッドに渡すことにより、一括挿入もサポートしています。

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