Як я можу одночасно зберігати кілька документів у Mongoose / Node.js?


78

На даний момент я використовую Save, щоб додати один документ. Припустимо, у мене є масив документів, які я хочу зберігати як окремі об’єкти. Чи є спосіб додати їх усіх одним викликом функції, а потім отримати один зворотний виклик, коли це буде зроблено? Я міг би додати всі документи індивідуально, але керування зворотними викликами для роботи, коли все зроблено, було б проблематичним.


Вам потрібно контролювати потік коду, використовуючи якусь асинхронну бібліотеку, таку як async. (є паралельна функція, і після завершення викликається зворотний виклик)
Рісто Новік,

Відповіді:


38

У Mongoose ще не реалізовані групові вставки (див. Випуск №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, будь ласка?
Ману

5
Я не думаю, що це "одночасно". Кожне збереження не викликається до завершення попереднього.
Тед Бігем,

Правда. Більш одночасним підходом було б, наприклад, вимкнути всі saves, зачекати, поки всі зателефонують за своїм зворотним викликом і повернути масив результатів. Ви можете використовувати для цього асинхронізацію або якийсь обіцяний інтерфейс.
diversario

коли цей стан if (--total)буде помилковим?
Gobliins

1
Я думаю, що наведена вище відповідь дуже стара. Існує метод з назвою insertMany () у мангусті. Перевірка mongoosejs.com/docs/api.html#model_Model.insertMany
Epsi95

92

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

Редагувати: Як багато хто зазначав, це не виконує справжню групову вставку - це просто приховує складність saveбагаторазового виклику самостійно. Нижче наведено відповіді та коментарі, що пояснюють, як використовувати власне драйвер Mongo для досягнення групової вставки в інтересах продуктивності.


13
Примітка: Це не ВСТУПНА вставка - основна реалізація мангуста робить цикли по всіх елементах і фіксує їх по одному.
зовні2344

1
^ це дуже важливо, оскільки це може серйозно вплинути на продуктивність тих, хто інтенсивно його використовує.
Lino Silva

1
Відповідь для Аарона Гекмана 2011: насправді не так. Model.create (doc1 [, docN], зворотний виклик) тут начебто допомагає, але все ще викликає model.save для вас на кожному. Якщо під терміном "швидше" ви маєте на увазі "обійти всі мангустські хуки та перевірку", тоді ви можете перейти до власного драйвера і використовувати його безпосередньо: Movie.collection.insert (docs, options, callback) github.com/christkv/node-mongodb -native / blob / master / lib / mongodb /…
arcseldon

У світлі всіх коментарів і нових відповідей щодо цього я змінив своє, щоб вказати на заперечення щодо масової вставки та продуктивності. Однак я відчуваю, що змушений зауважити, що Хоа ніколи не згадував продуктивність як причину цього питання - вони просто хотіли уникати очікування успіху кількох зворотних дзвінків.
Pascal Zajac

1
Я хочу наголосити, що це НЕ найкращий спосіб робити масові вставки, якщо ви маєте справу з великою кількістю документів . Див. Stackoverflow.com/a/24848148/778272, який містить краще пояснення.
Lucio Paiva

58

Mongoose 4.4 додав метод, який називається insertMany

Ярлик для перевірки масиву документів та вставки їх у MongoDB, якщо всі вони дійсні. Ця функція швидша, ніж .create (), оскільки вона надсилає на сервер лише одну операцію, а не одну для кожного документа.

Цитування vkarpov15 від питання # 723 :

Компроміси полягають у тому, що insertMany () не запускає хуки попереднього збереження, але він повинен мати кращу продуктивність, оскільки він робить лише 1 зворотний шлях до бази даних, а не 1 для кожного документа.

Підпис методу ідентичний create:

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

Або, з обіцянками:

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

Дякую за це. Він говорить, що вставить їх, якщо всі вони дійсні; чи означає це, якщо хтось зазнає невдачі, всі зазнають невдачі?
Арон

Це об’ємна операція, але вона не є атомною. Я не впевнений, як це робить Mongoose, і зараз він не може перевірити, але він повинен повернути кількість успішних записів. Детальніше в документації MongoDB: docs.mongodb.com/manual/reference/method/…
Pier-Luc Gendreau

4
Якщо хтось не вдається вставити, багато хто нічого не вставляє, я провів тести
shontauro

у ситуаціях, коли вам потрібно перевірити дублікати вставних документів, багато хто може завадити. здається, це працює нормально, якщо _id вже вказаний у нових документах; він викидає дублікат помилки для тих
zinoadidi

26

Групові вставки в Mongoose можна виконати за допомогою .insert (), якщо вам не потрібен доступ до проміжного програмного забезпечення.

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

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


1
Відповідь для Аарона Гекмана 2011: насправді. Model.create (doc1 [, docN], зворотний виклик) тут начебто допомагає, але все ще викликає model.save для вас на кожному. Якщо під терміном "швидше" ви маєте на увазі "обійти всі мангустські хуки та перевірку", тоді ви можете перейти до власного драйвера і використовувати його безпосередньо: Movie.collection.insert (docs, options, callback) github.com/christkv/node-mongodb -native / blob / master / lib / mongodb /…
arcseldon

1
Я постійно бачу цю відповідь, але це насправді не є "мангустським" способом робити щось. Це повністю обходить моделі Mongoose. Якщо у вас встановлені значення за замовчуванням для деяких полів у мангустських моделях, вони будуть проігноровані та не вставлені в БД.
Nahn,

1
Як я використовую Model.collection.insertв Mongoose? Надайте приклад.
Steve K

1
Я знаю, що на цей підхід є деякі критики, але насправді це найкраща (якщо не єдина ) відповідь, якщо ви маєте справу з величезною кількістю документів . Ця інша відповідь (http://stackoverflow.com/a/24848148/778272) пояснює, чому це краще, і наводить приклад.
Lucio Paiva

хто-небудь може підказати, які варіанти, можливо?
Нушад,

13

Використовуйте асинхронну паралель, і ваш код буде виглядати так:

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

Оскільки в Mongoose домовленість така ж, як і в async (помилка, зворотний виклик), вам не потрібно обертати їх у своїх зворотних викликах, просто додайте свої збережені дзвінки в масив, і ви отримаєте зворотний дзвінок, коли все буде закінчено.

Якщо ви використовуєте mapLimit, ви можете контролювати, скільки документів потрібно зберігати паралельно. У цьому прикладі ми зберігаємо паралельно 10 документів, поки всі елементи не будуть успішно збережені.

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

2
Цікаво - не могли б ви навести реальний приклад, придатний для використання, з символом myArray; тоді як myArray має 10 мільйонів предметів.
Steve K

8

Я знаю, що це старе запитання, але мене турбує те, що тут немає належних правильних відповідей. Більшість відповідей просто говорять про перебір усіх документів та збереження кожного з них окремо, що є ПОРОШОЮ ідеєю, якщо у вас є кілька документів, і процес повторюється навіть для одного з багатьох запитів.

MongoDB спеціально batchInsert()запрошує вставити кілька документів, і це слід використовувати з власного драйвера mongodb. Mongoose побудований на цьому драйвері, і він не підтримує пакетні вставки. Це, мабуть, має сенс, оскільки це має бути інструментом моделювання документообігу для MongoDB.

Рішення: Mongoose постачається з рідним драйвером MongoDB. Ви можете використовувати цей драйвер, вимагаючи його require('mongoose/node_modules/mongodb')(не надто впевнений у цьому, але ви завжди можете встановити mongodb npm знову, якщо він не працює, але я думаю, що повинен), а потім зробити належнеbatchInsert


2
Неправильно, відповідь Паскаля повністю пропускає сенс. Люди, яким потрібна об’ємна вставка, як правило, потрібні, оскільки вони хочуть вставити 10 000 000 предметів за один раз. Без масової вставки операція, яка триватиме кілька секунд, може зайняти години. Model.create - це епічний провал, оскільки він видає себе масовою вставкою, але під капотом це просто цикл for.
user3690202

Тоді мангусту серйозно потрібна певна модернізація. Також їхні документи залишають БАГАТО бажаних.
Steve K

Я думаю, що питання @ Yashua вирішує це питання за допомогою базового mongodbдрайвера javascript.
Ехтеш Чоудхурі

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

Ви можете використовувати обіцянку, повернуту мангустом save, 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;

Потім використовуйте його з мангустом:

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 resolver undefined is not a function in Promise.js
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

Ось приклад використання MongoDB Model.collection.insert()безпосередньо в Mongoose. Зверніть увагу, що якщо у вас не так багато документів, скажімо, менше 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.