実際には、MongoDBは「デフォルト」では、_id
を含む「一意のキー」が含まれる重複データを作成しません。 (mongooseによってid
としてエイリアスされます 、ただしinsertMany()
では無視されます したがって、注意する必要があります)が、これには、本当に注意する必要があるというはるかに大きなストーリーがあります。 。
ここでの基本的な問題は、insertMany()
の「マングース」実装の両方が 基礎となるドライバーと同様に、現在、それを穏やかに言うために少し「中断」されています。ドライバーが「バルク」操作でエラー応答を渡す方法に少し矛盾があり、これは実際のエラー情報を実際に「適切な場所で探す」のではなく、「マングース」によって実際に悪化します。
欠落している「迅速な」部分は、{ ordered: false }
の追加です。 .insertMany()
の「バルク」操作に への呼び出しをラップするだけです。これを設定すると、リクエストの「バッチ」が実際に「完全に」送信され、エラーが発生したときに実行が停止しなくなります。
しかし、「mongoose」はこれをうまく処理しないため(また、ドライバーは「一貫して」)、基になるコールバックの「エラー」結果ではなく、「応答」で発生する可能性のある「エラー」を実際に探す必要があります。
>デモンストレーションとして:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const songSchema = new Schema({
_id: Number,
name: String
});
const Song = mongoose.model('Song', songSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
let docs = [
{ _id: 1, name: "something" },
{ _id: 2, name: "something else" },
{ _id: 2, name: "something else entirely" },
{ _id: 3, name: "another thing" }
];
mongoose.connect(uri,options)
.then( () => Song.remove() )
.then( () =>
new Promise((resolve,reject) =>
Song.collection.insertMany(docs,{ ordered: false },function(err,result) {
if (result.hasWriteErrors()) {
// Log something just for the sake of it
console.log('Has Write Errors:');
log(result.getWriteErrors());
// Check to see if something else other than a duplicate key, and throw
if (result.getWriteErrors().some( error => error.code != 11000 ))
reject(err);
}
resolve(result); // Otherwise resolve
})
)
)
.then( results => { log(results); return true; } )
.then( () => Song.find() )
.then( songs => { log(songs); mongoose.disconnect() })
.catch( err => { console.error(err); mongoose.disconnect(); } );
または、現在のLTSnode.jsにasync/await
があるので、おそらくもう少し良いでしょう :
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const songSchema = new Schema({
_id: Number,
name: String
});
const Song = mongoose.model('Song', songSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
let docs = [
{ _id: 1, name: "something" },
{ _id: 2, name: "something else" },
{ _id: 2, name: "something else entirely" },
{ _id: 3, name: "another thing" }
];
(async function() {
try {
const conn = await mongoose.connect(uri,options);
await Song.remove();
let results = await new Promise((resolve,reject) => {
Song.collection.insertMany(docs,{ ordered: false },function(err,result) {
if (result.hasWriteErrors()) {
// Log something just for the sake of it
console.log('Has Write Errors:');
log(result.getWriteErrors());
// Check to see if something else other than a duplicate key, then throw
if (result.getWriteErrors().some( error => error.code != 11000 ))
reject(err);
}
resolve(result); // Otherwise resolve
});
});
log(results);
let songs = await Song.find();
log(songs);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
いずれにせよ、書き込みが継続され、「重複キー」またはエラーコード11000
として知られるエラーに関連するエラーを尊重して「無視」することを示す同じ結果が得られます。 。 「安全な処理」とは、そのようなエラーを予期して破棄し、注意したい「その他のエラー」の存在を探すことです。また、残りのコードが続き、後続の.find()
を実行して実際に挿入されたすべてのドキュメントが一覧表示されます。 電話:
Mongoose: songs.remove({}, {})
Mongoose: songs.insertMany([ { _id: 1, name: 'something' }, { _id: 2, name: 'something else' }, { _id: 2, name: 'something else entirely' }, { _id: 3, name: 'another thing' } ], { ordered: false })
Has Write Errors:
[
{
"code": 11000,
"index": 2,
"errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
"op": {
"_id": 2,
"name": "something else entirely"
}
}
]
{
"ok": 1,
"writeErrors": [
{
"code": 11000,
"index": 2,
"errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
"op": {
"_id": 2,
"name": "something else entirely"
}
}
],
"writeConcernErrors": [],
"insertedIds": [
{
"index": 0,
"_id": 1
},
{
"index": 1,
"_id": 2
},
{
"index": 2,
"_id": 2
},
{
"index": 3,
"_id": 3
}
],
"nInserted": 3,
"nUpserted": 0,
"nMatched": 0,
"nModified": 0,
"nRemoved": 0,
"upserted": [],
"lastOp": {
"ts": "6485492726828630028",
"t": 23
}
}
Mongoose: songs.find({}, { fields: {} })
[
{
"_id": 1,
"name": "something"
},
{
"_id": 2,
"name": "something else"
},
{
"_id": 3,
"name": "another thing"
}
]
では、なぜこのプロセスなのですか?その理由は、基になる呼び出しが実際には両方のerr
を返すためです。 およびresult
コールバックの実装に示されているように、しかし返されるものに矛盾があります。これを行う主な理由は、成功した操作の結果だけでなく、エラーメッセージも含む「結果」を実際に表示するためです。
エラー情報とともに、nInserted: 3
「バッチ」のうち実際に書き込まれた数を示します。 insertedIds
はほとんど無視できます この特定のテストには実際に_id
を提供することが含まれていたため、ここにあります 値。別のプロパティにエラーの原因となる「一意の」制約がある場合、ここでの値は実際に成功した書き込みからの値のみになります。少し誤解を招くかもしれませんが、自分でテストして確認するのは簡単です。
述べたように、キャッチは別の例(async/await
)で示すことができる「矛盾」です。 リストの簡潔さのためだけに):
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const songSchema = new Schema({
_id: Number,
name: String
});
const Song = mongoose.model('Song', songSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
let docs = [
{ _id: 1, name: "something" },
{ _id: 2, name: "something else" },
{ _id: 2, name: "something else entirely" },
{ _id: 3, name: "another thing" },
{ _id: 4, name: "different thing" },
//{ _id: 4, name: "different thing again" }
];
(async function() {
try {
const conn = await mongoose.connect(uri,options);
await Song.remove();
try {
let results = await Song.insertMany(docs,{ ordered: false });
console.log('what? no result!');
log(results); // not going to get here
} catch(e) {
// Log something for the sake of it
console.log('Has write Errors:');
// Check to see if something else other than a duplicate key, then throw
// Branching because MongoError is not consistent
if (e.hasOwnProperty('writeErrors')) {
log(e.writeErrors);
if(e.writeErrors.some( error => error.code !== 11000 ))
throw e;
} else if (e.code !== 11000) {
throw e;
} else {
log(e);
}
}
let songs = await Song.find();
log(songs);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
すべて同じことですが、ここでエラーログがどのように記録されるかに注意してください:
Has write Errors:
{
"code": 11000,
"index": 2,
"errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
"op": {
"__v": 0,
"_id": 2,
"name": "something else entirely"
}
}
後続の.find()
を実行することで同じリストの継続を取得しても、「成功」情報はないことに注意してください。 そして出力を取得します。これは、実装が拒否の「スローされたエラー」にのみ作用し、実際のresult
を通過しないためです。 部。したがって、ordered: false
を要求したとしても 、最初のリストに示されているように、コールバックをラップしてロジックを自分で実装しない限り、完了した内容に関する情報は取得されません。
他の重要な「矛盾」は、「複数のエラー」がある場合に発生します。したがって、_id: 4
の追加値のコメントを解除します 与える:
Has write Errors:
[
{
"code": 11000,
"index": 2,
"errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
"op": {
"__v": 0,
"_id": 2,
"name": "something else entirely"
}
},
{
"code": 11000,
"index": 5,
"errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 4 }",
"op": {
"__v": 0,
"_id": 4,
"name": "different thing again"
}
}
]
ここでは、e.writeErrors
が存在する場合に「分岐」したコードを確認できます。 、1つがある場合は存在しません エラー。対照的に、以前のresponse
オブジェクトには両方のhasWriteErrors()
があります およびgetWriteErrors()
エラーが存在するかどうかに関係なく、メソッド。これが、より一貫性のあるインターフェースであり、err
を検査する代わりにそれを使用する必要がある理由です。 応答のみ。
MongoDB3.xドライバーの修正
この動作は、MongoDB3.6サーバーリリースと一致するように意図されたドライバーの次の3.xリリースで実際に修正されています。 err
で動作が変わります 応答は標準のresult
に似ています 、ただしもちろんBulkWriteError
として分類されます MongoError
の代わりに応答 現在はそうです。
それがリリースされるまで(そしてもちろん、その依存関係と変更が「マングース」実装に伝播されるまで)、推奨される一連のアクションは、有用な情報がresult
にあることを認識することです。 およびない err
。実際、コードはおそらくhasErrors()
を探す必要があります result
で 次にフォールバックしてerr
をチェックします 同様に、ドライバーに実装される変更に対応するために。
作成者注: このコンテンツと関連する読み物の多くは、実際には関数insertMany()unorderedですでに回答されています:エラーと結果の両方を取得する適切な方法は?そしてMongoDBNode.jsネイティブドライバーは静かに
bulkWrite
を飲み込みます 例外。しかし、これが現在のドライバー実装で例外を処理する方法であることが最終的に人々に浸透するまで、ここで繰り返して詳しく説明します。そして、正しい場所を見て、それに応じて処理するコードを書くと、実際に機能します。