Ve skutečnosti MongoDB ve „výchozím nastavení“ nevytvoří duplicitní data tam, kde je zapojen „jedinečný klíč“, z nichž _id
( s aliasem mongoose jako id
, ale ignorováno pomocí insertMany()
takže musíte být opatrní ), ale je zde mnohem větší příběh, který si opravdu musíte uvědomit .
Základním problémem je zde jak „mongoose“ implementace insertMany()
stejně jako podkladový ovladač jsou v současné době mírně řečeno trochu „borked“. Tato skutečnost je trochu nekonzistentní v tom, jak ovladač předává chybovou odezvu při „hromadných“ operacích, a to je ve skutečnosti umocněno tím, že „mongoose“ ve skutečnosti „nehledá na správném místě“ skutečné informace o chybě.
"Rychlá" část, kterou postrádáte, je přidání { ordered: false }
k operaci "Hromadné", jejíž .insertMany()
jednoduše zabalí hovor. Toto nastavení zajišťuje, že „dávka“ požadavků bude skutečně odeslána „zcela“ a nezastaví provádění, když dojde k chybě.
Ale protože to „mongoose“ nezvládá příliš dobře (ani ovladač „konzistentně“), musíme ve skutečnosti hledat možné „chyby“ v „odpovědi“ spíše než „chybový“ výsledek základního zpětného volání.
Jako ukázka:
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(); } );
Nebo možná trochu hezčí, protože aktuální LTS node.js má 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();
}
})()
V každém případě získáte stejný výsledek, který ukazuje, že oba zápisy pokračují a že s úctou „ignorujeme“ chyby související s „duplicitním klíčem“ nebo jinak známé jako kód chyby 11000
. „Bezpečné zacházení“ spočívá v tom, že takové chyby očekáváme a vyřadíme je, zatímco hledáme přítomnost „jiných chyb“, kterým bychom mohli chtít věnovat pozornost. Vidíme také, že zbytek kódu pokračuje a uvádí všechny dokumenty skutečně vložené provedením následujícího .find()
zavolejte:
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"
}
]
Proč tedy tento proces? Důvodem je to, že základní volání ve skutečnosti vrací obě err
a result
jak je znázorněno v implementaci zpětného volání, ale v tom, co je vráceno, existuje nekonzistence. Hlavním důvodem je to, abyste skutečně viděli „výsledek“, který obsahuje nejen výsledek úspěšné operace, ale také chybovou zprávu.
Spolu s informací o chybě je nInserted: 3
udávající, kolik z „dávky“ bylo skutečně napsáno. insertedIds
můžete v podstatě ignorovat zde, protože tento konkrétní test ve skutečnosti zahrnoval poskytnutí _id
hodnoty. V případě, že jiná vlastnost měla „jedinečné“ omezení, které způsobilo chybu, pak zde budou jediné hodnoty ty ze skutečných úspěšných zápisů. Trochu zavádějící, ale snadno si to otestujete a uvidíte sami.
Jak již bylo řečeno, háček je v „nesoudržnosti“, kterou lze demonstrovat na dalším příkladu ( async/await
pouze pro stručnost výpisu):
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();
}
})()
Všechno je to stejné, ale věnujte pozornost tomu, jak se chyba zaznamenává zde:
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"
}
}
Všimněte si, že neexistují žádné informace o "úspěchu", i když stejné pokračování výpisu získáme provedením následujícího .find()
a získání výstupu. Je to proto, že implementace působí pouze na "vyhozenou chybu" při odmítnutí a nikdy neprojde skutečným result
část. Takže i když jsme požádali o ordered: false
, nezískáme informace o tom, co bylo dokončeno, pokud sami nezabalíme zpětné volání a neimplementujeme logiku, jak je ukázáno v úvodních výpisech.
Další důležitá "nekonzistence" nastává, když je "více než jedna chyba". Takže odkomentování další hodnoty pro _id: 4
nám dává:
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"
}
}
]
Zde můžete vidět kód "rozvětvený" na přítomnost e.writeErrors
, který neexistuje, když existuje jeden chyba. Naproti tomu dřívější response
objekt má obě hasWriteErrors()
a getWriteErrors()
metod, bez ohledu na to, zda se vůbec vyskytuje nějaká chyba. To je tedy konzistentnější rozhraní a důvod, proč byste jej měli používat namísto kontroly err
samotná odpověď.
Opravy ovladače MongoDB 3.x
Toto chování je ve skutečnosti opraveno v nadcházející verzi 3.x ovladače, která se má shodovat s vydáním serveru MongoDB 3.6. Chování se změní v tom, že err
odpověď je více podobná standardnímu result
, ale samozřejmě klasifikován jako BulkWriteError
odpověď namísto MongoError
kterým v současnosti je.
Dokud to nebude uvolněno (a samozřejmě dokud se tato závislost a změny nepřenesou do implementace "mongoose"), pak doporučeným postupem je uvědomit si, že užitečné informace jsou ve result
a ne err
. Ve skutečnosti by váš kód pravděpodobně měl hledat hasErrors()
ve result
a poté nouzově zkontrolujte err
také, aby bylo možné zajistit implementaci změny v ovladači.
Poznámka pro autory: Velká část tohoto obsahu a souvisejícího čtení je ve skutečnosti již zodpovězena zde ve funkci insertMany() unordered:správný způsob, jak získat chyby i výsledek? a nativní ovladač MongoDB Node.js tiše spolkne
bulkWrite
výjimka. Ale opakování a rozvíjení zde, dokud si lidé konečně nevšimli, že toto je způsob, jakým zacházíte s výjimkami v aktuální implementaci ovladače. A skutečně to funguje, když se podíváte na správné místo a napíšete svůj kód, abyste s ním podle toho zacházeli.