Всъщност MongoDB по "подразбиране" няма да създава дублиращи се данни, където има "уникален ключ", от който _id
( псевдоним от mongoose като id
, но се игнорира от insertMany()
така че трябва да внимавате ), но това има много по-голяма история, с която наистина трябва да сте наясно .
Основният проблем тук е, че както "мангустата" реализация на insertMany()
както и основният драйвер в момента са меко казано малко "разбити". Тъй като има малко несъответствие в начина, по който драйверът предава отговора за грешка в „групови“ операции и това всъщност се усложнява от това, че „мангустът“ всъщност не „търси на правилното място“ за действителната информация за грешката.
"Бързата" част, която ви липсва, е добавянето на { ordered: false }
към операцията "Bulk", от която .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(); } );
Или може би малко по-хубав, тъй като текущият LTS node.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
, което не съществува, когато имаединен грешка. За разлика от по-ранния response
обектът има и двата hasWriteErrors()
и getWriteErrors()
методи, независимо от наличието на някаква грешка. Така че това е по-последователният интерфейс и причината, поради която трябва да го използвате, вместо да проверявате err
отговор само.
Поправки на драйвери на MongoDB 3.x
Това поведение всъщност е фиксирано в предстоящата версия 3.x на драйвера, която трябва да съвпада с версията на сървъра MongoDB 3.6. Поведението се променя в това err
отговорът е по-подобен на стандартния result
, но разбира се класифициран като BulkWriteError
отговор вместо MongoError
което е в момента.
Докато това не бъде пуснато (и разбира се, докато тази зависимост и промени не бъдат разпространени в реализацията на "mongoose"), тогава препоръчителният начин на действие е да сте наясно, че полезната информация е в result
ине err
. Всъщност вашият код вероятно трябва да търси hasErrors()
в result
и след това обратно, за да проверите err
също така, за да се погрижи промяната да бъде въведена в драйвера.
Забележка на авторите: Голяма част от това съдържание и свързаното четене всъщност вече е отговорено тук във Функция insertMany() unorreded:правилен начин да получите както грешките, така и резултата? и роден драйвер на MongoDB Node.js безшумно поглъща
bulkWrite
изключение. Но повтаряйки и усъвършенствайки тук, докато най-накрая не стане ясно на хората, че това е начинът, по който се справяте с изключенията в текущата реализация на драйвера. И наистина работи, когато погледнете на правилното място и напишете кода си, за да се справите съответно с него.