Som noterats i kommentaren tidigare har du två grundläggande tillvägagångssätt för att ta reda på om något "skapades" eller inte. Dessa är antingen till:
-
Returnera
rawResult
i svaret och kontrolleraupdatedExisting
egenskap som talar om för dig om det är en "upsert" eller inte -
Ange
new: false
så att "inget dokument" faktiskt returneras som resultat när det faktiskt är en "upsert"
Som en lista för att demonstrera:
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()
}
})()
Och utdata:
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"
}
Så det första fallet tar faktiskt hänsyn till den här koden:
User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
)
De flesta alternativ är standard här som "alla" "upsert"
åtgärder kommer att resultera i att fältinnehållet används för att "matcha" (dvs. username
) är "alltid" skapat i det nya dokumentet, så du behöver inte $set
det fältet. För att faktiskt inte "modifiera" andra fält vid efterföljande förfrågningar kan du använda $setOnInsert
, som bara lägger till dessa egenskaper under en "upsert"
åtgärd där ingen matchning hittas.
Här är standarden new: true
används för att returnera det "modifierade" dokumentet från åtgärden, men skillnaden ligger i rawResult
som visas i det returnerade svaret:
{
"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
}
}
}
Istället för ett "mangosdokument" får du själva "råa" svar från föraren. Det faktiska dokumentinnehållet är under "value"
egenskapen, men det är "lastErrorObject"
vi är intresserade av.
Här ser vi egenskapen updatedExisting: false
. Detta indikerar att "ingen matchning" faktiskt hittades, så ett nytt dokument "skapades". Så du kan använda detta för att fastställa att skapandet faktiskt hände.
När du utfärdar samma frågealternativ igen blir resultatet annorlunda:
{
"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
}
}
}
updatedExisting
värdet är nu true
, och detta beror på att det redan fanns ett dokument som matchade username: 'Bill'
i frågesatsen. Detta talar om för dig att dokumentet redan fanns där, så du kan sedan förgrena din logik för att returnera ett "Error" eller vilket svar du vill.
I det andra fallet kan det vara önskvärt att "inte" returnera det "råa" svaret och istället använda ett returnerat "mangosdokument". I det här fallet ändrar vi värdet till att vara new: false
utan rawResult
alternativ.
User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
)
Det mesta av samma saker gäller förutom att nu är handlingen originalet dokumentets tillstånd returneras i motsats till det "modifierade" tillståndet för dokumentet "efter" åtgärden. Därför när det inte finns något dokument som faktiskt matchar "query"-satsen, är det returnerade resultatet null
:
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null // <-- Got null in response :(
Detta talar om för dig att dokumentet "skapades", och det kan argumenteras för att du redan vet vad innehållet i dokumentet ska vara eftersom du skickade dessa data med uttalandet (helst i $setOnInsert
). Poängen är att du redan vet vad du ska returnera "ska" du behöver för att faktiskt returnera dokumentinnehållet.
Däremot returnerar ett "hittat" dokument "ursprungligt tillstånd" som visar dokumentet "innan" det ändrades:
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
Därför alla svar som är "inte null
" är därför en indikation på att dokumentet redan fanns, och återigen kan du förgrena din logik beroende på vad som faktiskt togs emot som svar.
Så det är de två grundläggande tillvägagångssätten till det du frågar om, och de "fungerar verkligen"! Och precis som det är demonstrerat och reproducerbart med samma uttalanden här.
Tillägg - Reservera dubblettnyckel för felaktiga lösenord
Det finns ytterligare ett giltigt tillvägagångssätt som också antyds i den fullständiga listan, som i huvudsak är att helt enkelt .insert()
( eller .create()
från mongoose models ) nya data och har ett "duplicerat nyckel"-felkast där den "unika" egenskapen per index faktiskt påträffas. Det är ett giltigt tillvägagångssätt men det finns ett särskilt användningsfall i "användarvalidering" som är en praktisk logikhantering, och det är "validering av lösenord".
Så det är ett ganska vanligt mönster att hämta användarinformation med username
och password
kombination. I fallet med en "upsert" berättigar denna kombination som "unik" och därför görs ett "insert" försök om ingen matchning hittas. Det är just detta som gör matchning av lösenordet till en användbar implementering här.
Tänk på följande:
// 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 }
);
Vid första försöket har vi faktiskt inget username
för "Fred"
, så att "upsert" skulle inträffa och alla andra saker som redan beskrivits ovan råkar identifiera om det var en skapelse eller ett hittat dokument.
Påståendet som följer använder samma username
värde men ger ett annat lösenord än det som registreras. Här försöker MongoDB "skapa" det nya dokumentet eftersom det inte matchade kombinationen, utan eftersom username
förväntas vara "unique"
du får ett "Duplicate key error":
{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }
Så vad du bör inse är att du nu får tre villkor att utvärdera "gratis". Att vara:
- "Upsert" spelades in av antingen
updatedExisting: false
ellernull
resultat beroende på metoden. - Du vet att dokumentet (genom kombinationen) "finns" via antingen
updatedExisting: true
eller där dokumentet returnerar var "intenull
". - Om
password
tillhandahållet matchade inte det som redan fanns förusername
, då skulle du få "dupliceringsnyckelfelet" som du kan fånga och svara i enlighet med detta, och informera användaren som svar att "lösenordet är felaktigt".
Allt detta från ett begäran.
Det är det främsta skälet till att använda "upserts" i motsats till att bara kasta inserts på en samling, eftersom du kan få olika förgrening av logiken utan att göra ytterligare förfrågningar till databasen för att avgöra "vilket" av dessa villkor som ska vara det faktiska svaret.