I själva verket kommer MongoDB som "default" inte att skapa dubbletter av data där det finns en "unik nyckel" inblandad, varav _id
( alias av mongoose som id
, men ignoreras av insertMany()
så du måste vara försiktig ), men det finns en mycket större historia i detta som du verkligen måste vara medveten om .
Det grundläggande problemet här är att både "mongoose"-implementeringen av insertMany()
såväl som den underliggande drivkraften är för närvarande lite "borked" för att uttrycka det milt. Att det finns lite inkonsekvens i hur föraren skickar felsvaret i "bulk"-operationer och detta förvärras faktiskt av att "mangus" inte riktigt "söker på rätt plats" efter den faktiska felinformationen.
Den "snabba" delen du saknar är tillägget av { ordered: false }
till "Bulk"-operationen som .insertMany()
slår helt enkelt ett samtal till. Genom att ställa in detta säkerställs att "batchen" av förfrågningar faktiskt skickas "helt" och inte stoppar exekveringen när ett fel uppstår.
Men eftersom "mongoose" inte hanterar detta särskilt bra (och inte heller föraren "konsekvent") måste vi faktiskt leta efter möjliga "fel" i "svaret" snarare än "fel"-resultatet av den underliggande återuppringningen.
Som en demonstration:
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(); } );
Eller kanske lite trevligare eftersom nuvarande LTS node.js har 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();
}
})()
I alla fall får du samma resultat som visar att skrivningar både fortsätter och att vi respektfullt "ignorerar" fel som är relaterade till en "duplicerad nyckel" eller annars känd som felkod 11000
. Den "säkra hanteringen" är att vi förväntar oss sådana fel och kasserar dem samtidigt som vi letar efter förekomsten av "andra fel" som vi kanske bara vill uppmärksamma. Vi ser också att resten av koden fortsätter och listar alla dokument som faktiskt infogats genom att köra en efterföljande .find()
ring:
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"
}
]
Så varför denna process? Anledningen är att det underliggande anropet faktiskt returnerar både err
och result
som visas i callback-implementeringen men det finns en inkonsekvens i vad som returneras. Den främsta anledningen till att göra detta är så att du faktiskt ser "resultatet", som inte bara har resultatet av den lyckade operationen, utan också felmeddelandet.
Tillsammans med felinformationen finns nInserted: 3
anger hur många av "batchen" som faktiskt skrevs. Du kan i stort sett ignorera insertedIds
här eftersom just detta test innebar att faktiskt tillhandahålla _id
värden. Om en annan egenskap hade den "unika" begränsningen som orsakade felet, skulle de enda värdena här vara de från faktiska lyckade skrivningar. Lite missvisande, men lätt att testa och se själv.
Som nämnts är haken "inkosistens" som kan demonstreras med ett annat exempel ( async/await
endast för kortfattad information):
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();
}
})()
Allt ungefär samma sak, men var uppmärksam på hur felet loggas här:
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"
}
}
Observera att det inte finns någon "framgångsinformation", även om vi får samma fortsättning på listningen genom att göra efterföljande .find()
och få utdata. Detta beror på att implementeringen bara agerar på det "kastade felet" vid avvisning och aldrig passerar genom det faktiska result
del. Så även om vi bad om ordered: false
, vi får inte information om vad som slutfördes om vi inte avslutar återuppringningen och implementerar logiken själva, som visas i de första listorna.
Den andra viktiga "inkonsekvensen" inträffar när det finns "mer än ett fel". Så avkommenterar tilläggsvärdet för _id: 4
ger oss:
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"
}
}
]
Här kan du se koden "förgrenad" på förekomsten av e.writeErrors
, som inte finns när det finns en fel. Däremot det tidigare response
objektet har både hasWriteErrors()
och getWriteErrors()
metoder, oavsett om det finns några fel alls. Så det är det mer konsekventa gränssnittet och anledningen till att du bör använda det istället för att inspektera err
enbart svar.
MongoDB 3.x-drivrutinsfixar
Detta beteende är faktiskt fixat i den kommande 3.x-versionen av drivrutinen som är tänkt att sammanfalla med MongoDB 3.6-serverversionen. Beteendet ändras genom att err
svar är mer besläktat med result
, men naturligtvis klassad som en BulkWriteError
svar istället för MongoError
vilket det är för närvarande.
Tills det släpps (och givetvis tills det beroendet och ändringarna sprids till "mongoose"-implementeringen ), är det rekommenderade tillvägagångssättet att vara medveten om att den användbara informationen finns i result
och inte err
. Faktum är att din kod förmodligen borde leta efter hasErrors()
i result
och sedan fallback för att kontrollera err
också, för att tillgodose förändringen som ska implementeras i drivrutinen.
Obs för författare: Mycket av detta innehåll och relaterad läsning är faktiskt redan besvarad här på Function insertMany() unordered:korrekt sätt att få både felen och resultatet? och MongoDB Node.js inbyggda drivrutin sväljer tyst
bulkWrite
undantag. Men upprepar och utvecklar här tills det äntligen sjunker in för folk att det är så man hanterar undantag i den nuvarande drivrutinsimplementeringen. Och det fungerar faktiskt när du tittar på rätt ställe och skriver din kod för att hantera den därefter.