似乎将有序选项设置为false的MongoDB insertMany()函数比有序选项设置为true可以更有效地插入文档。即使几个文档无法插入,它也可以继续插入文档。

但是我发现没有一种干净的方法来获取每个失败文档的错误和总体命令结果。

(顺便说一句,我正在使用Node.js驱动程序API 2.2。从现在开始,我将引用驱动程序的源代码:http://mongodb.github.io/node-mongodb-native/2.2/api/lib_collection.js.html)

首先,如果使用Promise,则无法同时获取错误和结果。在源代码行540上,insertMany()返回错误或结果-不同时返回两者,而bulkWrite()回调在源代码行703上均返回错误和结果。

其次,如果使用了回调,情况会变得更糟。当bulkWrite()调用带有错误和结果的回调时,insertMany()调用带有错误和结果的回调,但结果是BulkWrite的结果,而不是正确转换为InsertManyResults的结果。请参见源代码行535。我认为这是一个错误。

甚至对于bulkWrite(),当错误数为1时,它也无法将结果正确转换为其格式。请参阅源代码行669。我认为这也是一个错误。

现在,我认为Node.js驱动程序根本不准备处理这种情况。

目前,似乎还没有办法正确获得错误和结果。

我对吗?

更新

我已经运行了一个基于Neil Lunn的答案的测试代码。自从我
Node.js(4.4.5)不了解异步/等待,我不得不用显式的Promises重写测试代码。

测试代码如下:

function doTest()
{
    var MongoClient = require('mongodb').MongoClient;
    var testData = [ 1,2,2,3,3,4,5,6,7,8,9 ];
    var db;

    return MongoClient.connect('mongodb://127.0.0.1/test')
    .then(function (_db)
    {
        db = _db;
        return db.createCollection('test');
    })
    .then(function ()
    {
        return db.collection('test').deleteMany({})
        .then(function ()
        {
            return db.collection('test').insertMany(
                testData.map(function (_id)
                {
                    return { _id: _id };
                }),
                { ordered: false })
            .then(function (result)
            {
                console.log('Promise: result', result);
            }, function (err)
            {
                console.log('Promise: error', err);
            });
        })
        .then(function ()
        {
            return db.collection('test').deleteMany({});
        })
        .then(function ()
        {
            return new Promise(function (resolve, reject)
            {
                return db.collection('test').insertMany(
                    testData.map(function (_id)
                    {
                        return { _id: _id };
                    }),
                    { ordered: false },
                    function (err, result)
                {
                    console.log('callback: error', err);
                    console.log('callback: result', result);
                    console.log('callback: result.hasWriteErrors', result.hasWriteErrors());
                    console.log('callback: result.getWriteErrors',
                        JSON.stringify(result.getWriteErrors(), null, 2));
                    resolve();
                });
            });
        });
    })
    .catch(function (err)
    {
        console.log('catch', err);
    })
    .then(function ()
    {
        db.close();
    });
}
doTest();

结果如下:
Promise: error { [MongoError: write operation failed]
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors:
   [ { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
callback: error { [MongoError: write operation failed]
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors:
   [ { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
callback: result { ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function],
  insertedCount: 9,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds:
   { '0': 1,
     '1': 2,
     '2': 2,
     '3': 3,
     '4': 3,
     '5': 4,
     '6': 5,
     '7': 6,
     '8': 7,
     '9': 8,
     '10': 9 },
  n: 9 }
callback: result.hasWriteErrors true
callback: result.getWriteErrors [
  {
    "code": 11000,
    "index": 2,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 2 }",
    "op": {
      "_id": 2
    }
  },
  {
    "code": 11000,
    "index": 4,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  }
]

现在,我再次运行代码,对testData变量进行如下修改:
var testData = [ 1,2,3,3,4,5,6,7,8,9 ];

在这种情况下,错误的数量将是1,而不是2,因为删除了重复的“2”。

结果如下:
Promise: error { [MongoError: E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }]
  name: 'MongoError',
  message: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  driver: true,
  code: 11000,
  index: 3,
  errmsg: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  getOperation: [Function],
  toJSON: [Function],
  toString: [Function] }
callback: error { [MongoError: E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }]
  name: 'MongoError',
  message: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  driver: true,
  code: 11000,
  index: 3,
  errmsg: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  getOperation: [Function],
  toJSON: [Function],
  toString: [Function] }
callback: result { ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function] }
callback: result.hasWriteErrors true
callback: result.getWriteErrors [
  {
    "code": 11000,
    "index": 3,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  }
]

错误和结果的格式与第一次运行完全不同。
  • 该错误没有writeErrors字段。
  • 结果没有“已转换”字段。 (inserttedCount,matchedCount等),这是驱动程序源代码行669上的“错误”,如上所述。

  • 并且在两次测试运行中,结果参数的类型都不是Collection~insertWriteOpResult。第一个是Collection~bulkWriteOpCallback,第二个是更多内部的。因此,在这种情况下,API文档是错误的。正如我在上文所述,这是由535和669行上的“错误”引起的。

    因此,即使可以使用结果(实际上,如Neil Lunn所说,结果具有hasWriteErrors()getWriteErrors()),由于未记录此行为,我怀疑可以在以后的版本中对其进行更改而不另行通知,并且我的代码将被破坏。

    最佳答案

    该问题实际上仅与“ promise ”的解决方式以及错误信息的传递方式有关,但是,真正的核心问题是实际上是,实际上,当任何“批量”返回时,错误和结果信息都“返回” “操作设置为{ ordered: false }。如NODE-1158所述,该问题已在3.x发行版的驱动程序中得到解决,该驱动程序还包含指向future分支中用于修复该问题的提交的链接。

    为此,“解决方法”要注意的是,在任何此类方法的“回调”调用中,返回的BulkWriteResult对象中均存在错误信息,这是结果(请注意insertMany()甚至bulkWrite()实际上都包装了底层Bulk API Implementation) )。

    用 list 进行演示:

    const MongoClient = require('mongodb').MongoClient;
    
    const uri = 'mongodb://localhost/test';
    const testData = [1,2,3,3,4,5,6,6,7,8,9];
    
    (async function() {
    
      let db;
    
      try {
    
        db = await MongoClient.connect(uri);
    
        await db.collection('test').remove();
    
        // Expect an error here - but it's just the errors
        try {
          let result = await db.collection('test').insertMany(
            testData.map( _id => ({ _id }) ),
            { "ordered": false }
          );
          console.log(result);   // never gets here
        } catch(e) {
          console.dir(e);
          console.log(JSON.stringify(e.writeErrors,undefined,2));
        }
    
        await db.collection('test').remove();
        // Wrapped callback so we see what happens
    
        try {
          let result = await new Promise((resolve,reject) =>
            db.collection('test').insertMany(
              testData.map( _id => ({ _id }) ),
              { "ordered": false },
              (err,result) => {
                if (err) reject(result);    // Because the errors are here as well
                resolve(result);
              }
            )
          );
          console.log(result);  // Never gets here
        } catch(e) {
          console.dir(e);
          console.log(e.hasWriteErrors());
          console.log(JSON.stringify(e.getWriteErrors(),undefined,2));
        }
    
      } catch(e) {
        console.error(e);
      } finally {
        db.close();
      }
    
    })();
    

    因此,这里有两个代码块尝试将insertMany()与值列表一起使用,这些值将对某些值产生重复的键错误。

    在第一次尝试中,我们使用默认的Promise返回值,正如驱动程序的已实现代码所指示的那样,只是将包装其err语句的方法中的reject()回调结果传递给它。这意味着我们转到此处的catch块并产生错误信息作为输出:
    { MongoError: [object Object]
        at Function.MongoError.create (/home/neillunn/projects/bulkerror/node_modules/mongodb-core/lib/error.js:31:11)
        at toError (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:139:22)
        at /home/neillunn/projects/bulkerror/node_modules/mongodb/lib/collection.js:701:23
        at handleCallback (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:120:56)
        at /home/neillunn/projects/bulkerror/node_modules/mongodb/lib/bulk/unordered.js:465:9
        at handleCallback (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:120:56)
        at resultHandler (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/bulk/unordered.js:413:5)
        at /home/neillunn/projects/bulkerror/node_modules/mongodb-core/lib/connection/pool.js:469:18
        at _combinedTickCallback (internal/process/next_tick.js:131:7)
        at process._tickCallback (internal/process/next_tick.js:180:9)
      name: 'MongoError',
      message: 'write operation failed',
      driver: true,
      code: 11000,
      writeErrors:
       [ WriteError {
           code: [Getter],
           index: [Getter],
           errmsg: [Getter],
           getOperation: [Function],
           toJSON: [Function],
           toString: [Function] },
         WriteError {
           code: [Getter],
           index: [Getter],
           errmsg: [Getter],
           getOperation: [Function],
           toJSON: [Function],
           toString: [Function] } ] }
    [
      {
        "code": 11000,
        "index": 3,
        "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
        "op": {
          "_id": 3
        }
      },
      {
        "code": 11000,
        "index": 7,
        "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 6 }",
        "op": {
          "_id": 6
        }
      }
    ]
    

    请注意,这是一个包装好的MongoError,尽管我们设置了{ ordered: false },但响应中没有“结果”信息。在对错误信息的详细检查中,我们可以看到WriteError列表确实包含有关所产生的每个重复键错误的详细信息。

    因此,该批次中的所有内容均已成功写入,且未引发错误,但未从Promise 中获得的中报告任何事件。但是,对于底层方法却并非如此,该方法仍使用回调来实现。

    第二次尝试是“手动”包装此回调,因此实际上我们可以通过更改行为并将存在resultreject对象传递给err来查看结果。这告诉我们一个不同的故事:
    BulkWriteResult {
      ok: [Getter],
      nInserted: [Getter],
      nUpserted: [Getter],
      nMatched: [Getter],
      nModified: [Getter],
      nRemoved: [Getter],
      getInsertedIds: [Function],
      getUpsertedIds: [Function],
      getUpsertedIdAt: [Function],
      getRawResponse: [Function],
      hasWriteErrors: [Function],
      getWriteErrorCount: [Function],
      getWriteErrorAt: [Function],
      getWriteErrors: [Function],
      getLastOp: [Function],
      getWriteConcernError: [Function],
      toJSON: [Function],
      toString: [Function],
      isOk: [Function],
      insertedCount: 9,
      matchedCount: 0,
      modifiedCount: 0,
      deletedCount: 0,
      upsertedCount: 0,
      upsertedIds: {},
      insertedIds:
       { '0': 1,
         '1': 2,
         '2': 3,
         '3': 3,
         '4': 4,
         '5': 5,
         '6': 6,
         '7': 6,
         '8': 7,
         '9': 8,
         '10': 9 },
      n: 9 }
    true
    [
      {
        "code": 11000,
        "index": 3,
        "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
        "op": {
          "_id": 3
        }
      },
      {
        "code": 11000,
        "index": 7,
        "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 6 }",
        "op": {
          "_id": 6
        }
      }
    ]
    

    由于我们没有传递回err,因此现在在catch块中看到了BulkWriteResult。我们知道到达那里是因为在该块中运行的用于检查结果的特定代码。

    常规结果的确确实具有某些内容,例如修改或插入的计数以及insertedIds列表。从检查中我们还可以看到hasWriteErrors()返回true,并且我们可以获得序列化的WriteError列表,以便更好地查看。

    在3.x版中已修复

    如链接问题所述,实际的修复程序仅会在支持MongoDB 3.6的3.x驱动程序版本中提供。本质上,“修复”是在的“较低级别”完成的,而不是根本不返回BulkWriteResult,而是使err返回BulkWriteError

    实际上,这使事情与其他一些驱动程序已正确实现这一点的方式更加一致。老实说,它有点像传统的“Node 样式”回调中的“宿醉”,在该回调中总是返回“错误”和响应两者。

    因此,将其引入“只是一个错误”会使事情更加一致,并且可以像您通常期望的那样工作。

    顺便说一句,JIRA问题中引用的MongoDB Node.js native driver silently swallows bulkWrite exception.中的相关问题显示了实现中的实际bulkWrite()方法(不是“直接”表示insertMany()包装的内容)有一个稍有不同的问题,即实际上得到了“无错误”完全抛出该错误,因为代码期望resultnull,而不是所描述的。

    因此,在这种情况下情况恰好相反,在默认实现中,我们永远不会使用catch来获取Promise的异常。但是,应该采用完全相同的处理方法,方法是:手动包装回调并通过result通过reject发送err优先于null,而不是将二者都返回为ojit_code。

    按照上述说明解决它们,最好在新驱动程序可用后立即移至新驱动程序。无论如何应该很快。

    关于javascript - 函数insertMany()无序: proper way to get both the errors and the result?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/46841162/

    10-12 05:14