如何在Mongoose / Node.js中同时保存多个文档?


78

目前,我使用保存添加单个文档。假设我有一系列文档希望存储为单个对象。有没有一种方法可以通过单个函数调用将它们全部添加,然后在完成后获得单个回调?我可以单独添加所有文档,但是管理回调以解决所有问题。


您需要使用诸如async之类的异步库来控制代码流。(有并行函数,完成后将调用回调)
Risto Novik

Answers:


38

猫鼬尚未实现批量插入(请参阅问题#723)。

由于您知道要保存的文档数量,因此可以编写如下内容:

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很棒)。


2
能否使用q提供解决方案?
Manu 2015年

5
我认为这不是“并行的”。在上一个保存完成之前,不会调用每个保存。
Ted Bigham

真正。例如,更并发的方法是触发all save,等待所有人调用其回调并返回结果数组。您可以为此使用async或某​​些promise接口。
diversario

什么时候该条件if (--total)为假?
Gobliins

1
我认为以上答案很老。猫鼬中有一个名为insertMany()的方法。检查mongoosejs.com/docs/api.html#model_Model.insertMany
Epsi95

92

Mongoose现在确实支持将多个文档结构传递给Model.create。引用他们的API示例,它支持传递数组或对象的varargs列表,最后带有回调:

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

编辑:正如许多人所指出的那样,这并不能执行真正的批量插入-它只是掩盖了save自己多次调用的复杂性。下面提供了答案和注释,以解释如何出于性能考虑如何使用实际的Mongo驱动程序来实现批量插入。


13
注意:这不是大量插入-底层的猫鼬实现确实循环遍历所有元素并逐一提交。
outside2344 2014年

1
^这非常相关,因为它可能会严重影响那些频繁使用它的用户的性能。
Lino Silva

1
2011年亚伦·赫克曼(Aaron Heckman)的回应:并非如此。Model.create(doc1 [,docN],回调)在这里有所帮助,但它仍在调用model.save。如果“更快”表示“绕过所有猫鼬钩子和验证”,则可以下拉至本机驱动程序并直接使用它:Movie.collection.insert(docs,options,callback) github.com/christkv/node-mongodb -native / blob / master / lib / mongodb /…
arcseldon 2014年

鉴于对此的所有评论和新答案,我修正了我的观点,以指出有关批量插入和性能方面的异议。但是,我不得不指出,Hoa从未提到性能是问这个问题的原因-他们只是想避免等待多个回调成功。
Pascal Zajac

1
我要强调的是,如果要处理大量文档,这不是进行批量插入的最佳方法。请参阅stackoverflow.com/a/24848148/778272,其中包含更好的说明。
Lucio Paiva 2015年

58

猫鼬4.4添加了一种称为 insertMany

验证文档数组并将其全部插入MongoDB的快捷方式。此函数比.create()更快,因为它仅向服务器发送一个操作,而不是为每个文档发送一个操作。

从问题#723引用vkarpov15 :

折衷方案是insertMany()不会触发预保存的钩子,但是它应该具有更好的性能,因为它仅使数据库往返1次,而不是每个文档往返1次。

该方法的签名与create

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

或者,承诺:

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

谢谢你 它说如果它们都是有效的,它将插入它们。这是否意味着如果一个人失败了,那一切都会失败吗?
阿隆

这是批量操作,但不是原子操作。我不确定猫鼬是如何做到的,现在无法测试,但是它应该返回成功写入的次数。有MongoDB的文档中更多的细节:docs.mongodb.com/manual/reference/method/...
码头-吕克·Gendreau所言

4
如果插入失败,则很多都没有插入,我已经进行了测试
shontauro

在需要验证重复文档的情况下,插入许多文档可能会很麻烦。如果在新文档中已经指定了_id,它似乎可以正常工作;它会为这些对象引发重复错误
zinoadidi

26

除非需要访问中间件,否则可以使用.insert()进行Mongoose中的批量插入。

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

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


1
2011年亚伦·赫克曼(Aaron Heckman)的回应:并非如此。Model.create(doc1 [,docN],回调)在这里有所帮助,但它仍在调用model.save。如果“更快”表示“绕过所有猫鼬钩子和验证”,则可以下拉至本机驱动程序并直接使用它:Movie.collection.insert(docs,options,callback) github.com/christkv/node-mongodb -native / blob / master / lib / mongodb /…
arcseldon

1
我一直看到这个答案,但这并不是真正的“猫鼬”做事方式。这完全绕过了猫鼬模型。如果在猫鼬模型中为某些字段设置了默认值,则将忽略它们,并且不将其插入数据库中。
Nahn 2014年

1
如何Model.collection.insert在猫鼬中使用?请提供一个例子。
史蒂夫·K

1
我知道有人批评这种方法,但是如果您要处理大量文档,这实际上是最好的答案(如果不是唯一的话)。这个其他答案(http://stackoverflow.com/a/24848148/778272)解释了为什么更好,并给出了示例。
Lucio Paiva 2015年

谁能提出建议,也许吗?
Noushad

13

使用异步并行,您的代码将如下所示:

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

由于Mongoose中的约定与异步(err,回调)中的约定相同,因此您无需将它们包装在自己的回调中,只需将您的save调用添加到数组中,当所有操作完成后即可获得回调。

如果使用mapLimit,则可以控制要并行保存的文档数量。在此示例中,我们并行保存10个文档,直到成功保存所有项目。

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

2
有趣的是-您介意在现实世界中使用一个示例吗myArray?而myArray有1000万个项目。
Steve K

8

我知道这是一个老问题,但令我担心的是,这里没有正确正确的答案。大多数答案只是谈论遍历所有文档并分别保存每个文档,如果您有多个文档,这是一个糟糕的主意,并且对于许多请求中的一个甚至重复该过程。

MongoDB专门有一个batchInsert()用于插入多个文档的调用,应该在本机mongodb驱动程序中使用它。猫鼬基于此驱动程序构建,并且不支持批量插入。这可能很有意义,因为它应该是MongoDB的对象文档建模工具。

解决方案:Mongoose随附了本机MongoDB驱动程序。您可以通过要求使用该驱动程序require('mongoose/node_modules/mongodb')(对此不太确定,但是如果无法正常运行,可以随时再次安装mongodb npm,但我认为应该这样做),然后执行适当的操作batchInsert


2
错误的,帕斯卡的答案完全错了。需要批量插入的人往往会需要它,因为他们想一次插入10,000,000个项目。如果不批量插入,可能需要数秒钟的操作可能会花费数小时。Model.create是一次史诗般的失败,因为它伪装成是批量插入,但在幕后它只是一个for循环。
user3690202 2014年

然后,猫鼬非常需要进行一些修改。他们的文档也有很多不足之处。
史蒂夫·K

我认为@Yashua的问题是通过使用底层mongodbjavascript驱动程序解决的。
Ehtesh Choudhury 2014年

8

较新版本的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).');
}

5

这是不使用其他库的另一种方式(不包括错误检查)

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

3

您可以使用mongoose返回的promise savePromise在mongoose中并没有全部,但是您可以在此模块中添加功能。

创建一个增强所有人猫鼬承诺的模块。

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;

然后与猫鼬一起使用:

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

Uncaught TypeError:Promise.js中未定义Promise解析器不是函数
krishan kumar

1

使用insertMany功能插入许多文件。这仅将一个操作发送到服务器,并Mongoose在命中mongo服务器之前验证所有文档。默认情况下,Mongoose将按它们在数组中存在的顺序插入项目。如果您可以不维护任何命令,请设置ordered:false

重要-错误处理:

ordered:true验证和错误处理发生在一个组中时,意味着如果一个失败了,那么一切都会失败。

ordered:false验证和错误处理分别发生时,操作将继续。错误将以一系列错误报告。


0

添加一个名为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

这是一个老问题,但是当搜索“猫鼬插入文档数组”时,它首先出现在Google搜索结果中。

您可以使用两个选项model.create()[mongoose]和model.collection.insert()[mongodb]。在这里查看有关每个选项的优缺点的更详尽的讨论:

猫鼬(mongodb)批处理插入?


0

这是Model.collection.insert()直接在Mongoose中使用MongoDB的示例。请注意,如果您没有那么多文档(例如少于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: 'foo@bar.com', name: 'foo'}, {email: 'baz@bar.com', name: 'baz'}];
saveUsers(users).then(function() {
  // handle success case here
})
.fail(function(error) {
  // handle error case here
});

这是哪个猫鼬版本?
R01010010 '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.