Zoals eerder opgemerkt in het commentaar, heb je twee basisbenaderingen om te bepalen of iets "gemaakt" is of niet. Deze zijn ofwel:
-
Retourneer het
rawResult
in het antwoord en controleer deupdatedExisting
eigenschap die u vertelt of het een "upsert" is of niet -
Stel
new: false
in zodat "geen document" daadwerkelijk wordt geretourneerd in resultaat terwijl het eigenlijk een "upsert" is
Als een lijst om te demonstreren:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/thereornot';
mongoose.set('debug', true);
mongoose.Promise = global.Promise;
const userSchema = new Schema({
username: { type: String, unique: true }, // Just to prove a point really
password: String
});
const User = mongoose.model('User', userSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Shows updatedExisting as false - Therefore "created"
let bill1 = await User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
);
log(bill1);
// Shows updatedExisting as true - Therefore "existing"
let bill2 = await User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
);
log(bill2);
// Test with something like:
// if ( bill2.lastErrorObject.updatedExisting ) throw new Error("already there");
// Return will be null on "created"
let ted1 = await User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
);
log(ted1);
// Return will be an object where "existing" and found
let ted2 = await User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
);
log(ted2);
// Test with something like:
// if (ted2 !== null) throw new Error("already there");
// Demonstrating "why" we reserve the "Duplicate" error
let fred1 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'password' },
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
log(fred1); // null - so okay
let fred2 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
En de uitvoer:
Mongoose: users.remove({}, {})
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
"lastErrorObject": {
"n": 1,
"updatedExisting": false,
"upserted": "5adfc8696878cfc4992e7634"
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
"lastErrorObject": {
"n": 1,
"updatedExisting": true
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
Dus het eerste geval beschouwt deze code eigenlijk:
User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
)
De meeste opties zijn hier standaard als "alle" "upsert"
acties zullen ertoe leiden dat de veldinhoud wordt gebruikt om te "matchen" (d.w.z. de username
) is "altijd" gemaakt in het nieuwe document, dus u hoeft niet $set
dat veld. Om andere velden niet daadwerkelijk te "wijzigen" bij volgende verzoeken, kunt u gebruiken $setOnInsert
, die deze eigenschappen alleen toevoegt tijdens een "upsert"
actie waarbij geen match is gevonden.
Hier de standaard new: true
wordt gebruikt om het "aangepaste" document van de actie te retourneren, maar het verschil zit in de rawResult
zoals wordt getoond in het geretourneerde antwoord:
{
"lastErrorObject": {
"n": 1,
"updatedExisting": false,
"upserted": "5adfc8696878cfc4992e7634"
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
In plaats van een "mangoesdocument" krijg je het eigenlijke "ruwe" antwoord van de chauffeur. De daadwerkelijke documentinhoud staat onder de "value"
eigenschap, maar het is de "lastErrorObject"
waarin we geïnteresseerd zijn.
Hier zien we de eigenschap updatedExisting: false
. Dit geeft aan dat er daadwerkelijk "geen overeenkomst" is gevonden, dus is er een nieuw document "gemaakt". Je kunt dit dus gebruiken om te bepalen of de creatie daadwerkelijk heeft plaatsgevonden.
Wanneer u dezelfde query-opties opnieuw uitvoert, zal het resultaat anders zijn:
{
"lastErrorObject": {
"n": 1,
"updatedExisting": true // <--- Now I'm true
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
De updatedExisting
waarde is nu true
, en dit komt omdat er al een document was dat overeenkwam met de username: 'Bill'
in de query-instructie. Dit vertelt je dat het document er al was, dus je kunt je logica vertakken om een "Fout" of welk antwoord dan ook te retourneren.
In het andere geval kan het wenselijk zijn om het "onbewerkte" antwoord "niet" te retourneren en in plaats daarvan een geretourneerd "mangoestdocument" te gebruiken. In dit geval variëren we de waarde om new: false
. te zijn zonder de rawResult
optie.
User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
)
De meeste van dezelfde dingen zijn van toepassing, behalve dat de actie nu de originele . is status van het document wordt geretourneerd in tegenstelling tot de "gewijzigde" status van het document "na" de actie. Dus als er geen document is dat daadwerkelijk overeenkomt met de "query"-instructie, is het geretourneerde resultaat null
:
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null // <-- Got null in response :(
Dit vertelt u dat het document is "gemaakt", en het is aannemelijk dat u al weet wat de inhoud van het document zou moeten zijn, aangezien u die gegevens met de instructie hebt verzonden (idealiter in de $setOnInsert
). Punt is dat u al weet wat u moet retourneren "zou" u nodig hebben om de inhoud van het document daadwerkelijk te retourneren.
Een "gevonden" document daarentegen retourneert de "oorspronkelijke staat" en toont het document "vóór" het werd gewijzigd:
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
Daarom is elke reactie die "niet null
. is " is daarom een indicatie dat het document al aanwezig was, en nogmaals, je kunt je logica vertakken, afhankelijk van wat er daadwerkelijk als antwoord is ontvangen.
Dus dat zijn de twee basisbenaderingen van wat je vraagt, en ze "werken zeker"! En net zoals hier wordt aangetoond en reproduceerbaar met dezelfde uitspraken.
Addendum - Reserveer dubbele sleutel voor slechte wachtwoorden
Er is nog een geldige benadering waarnaar ook wordt gesuggereerd in de volledige lijst, die in wezen is om simpelweg .insert()
( of .create()
van mangoestmodellen) nieuwe gegevens en hebben een "duplicaatsleutel"-foutworp waar de "unieke" eigenschap per index daadwerkelijk wordt aangetroffen. Het is een geldige benadering, maar er is één specifiek gebruiksgeval in "gebruikersvalidatie", wat een handig stukje logica is, en dat is "het valideren van wachtwoorden".
Het is dus een vrij algemeen patroon om gebruikersinformatie op te halen met de username
en password
combinatie. In het geval van een "upsert" rechtvaardigt deze combinatie als "uniek" en daarom wordt een "insert" geprobeerd als er geen match wordt gevonden. Dit is precies wat het matchen van het wachtwoord hier een nuttige implementatie maakt.
Overweeg het volgende:
// Demonstrating "why" we reserve the "Duplicate" error
let fred1 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'password' },
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
log(fred1); // null - so okay
let fred2 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
Bij de eerste poging hebben we niet echt een username
voor "Fred"
, dus de "upsert" zou plaatsvinden en alle andere dingen zoals hierboven beschreven gebeuren om te identificeren of het een creatie of een gevonden document was.
De instructie die volgt gebruikt dezelfde username
waarde, maar geeft een ander wachtwoord dan wat is vastgelegd. Hier probeert MongoDB het nieuwe document te "maken", omdat het niet overeenkwam met de combinatie, maar omdat de username
is naar verwachting "unique"
u ontvangt een "Duplicate key error":
{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }
Dus je moet je realiseren dat je nu drie . krijgt voorwaarden te evalueren voor "gratis". Zijn:
- De "upsert" is geregistreerd door de
updatedExisting: false
ofnull
resultaat afhankelijk van de methode. - U weet dat het document ( door combinatie ) "bestaat" via de
updatedExisting: true
of waar het document retourneert "nietnull
". - Als het
password
opgegeven, kwam niet overeen met wat al bestond voor deusername
, dan krijgt u de "duplicaat-sleutelfout" die u kunt opsluiten en dienovereenkomstig kunt reageren, waarbij u de gebruiker als antwoord geeft dat het "wachtwoord onjuist is".
Dat alles van één verzoek.
Dat is de belangrijkste reden om "upserts" te gebruiken in plaats van simpelweg inserts naar een verzameling te gooien, omdat je verschillende vertakkingen van de logica kunt krijgen zonder extra verzoeken aan de database te doen om te bepalen "welke" van die voorwaarden het daadwerkelijke antwoord zou moeten zijn.