Както беше отбелязано в коментара по-рано, имате два основни подхода, за да разберете дали нещо е „създадено“ или не. Те са или към:
-
Върнете
rawResult
в отговора и проверетеupdatedExisting
свойство, което ви казва дали е "upsert" или не -
Задайте
new: false
така че „няма документ“ всъщност да се връща в резултат, когато всъщност е „upsert“
Като списък за демонстрация:
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()
}
})()
И резултатът:
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"
}
Така че първият случай всъщност разглежда този код:
User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
)
Повечето опции са стандартни тук като „всички“ "upsert"
действията ще доведат до използване на съдържанието на полето за „съвпадение“ (т.е. username
) е "винаги" създаден в новия документ, така че не е необходимо да $set
това поле. За да не "промените" други полета при следващи заявки, можете да използвате $setOnInsert
, който добавя тези свойства само по време на "upsert"
действие, при което не е намерено съвпадение.
Тук стандартът new: true
се използва за връщане на „модифицирания“ документ от действието, но разликата е в rawResult
както е показано във върнатия отговор:
{
"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
}
}
}
Вместо „монгустен документ“ получавате действителния „суров“ отговор от водача. Действителното съдържание на документа е под "value"
свойството, но това е "lastErrorObject"
ни интересува.
Тук виждаме свойството updatedExisting: false
. Това показва, че действително не е намерено „съвпадение“, поради което е „създаден“ нов документ. Така че можете да използвате това, за да определите, че създаването наистина се е случило.
Когато подадете отново същите опции за заявка, резултатът ще бъде различен:
{
"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
стойността вече е true
и това е така, защото вече имаше документ, който съответстваше на username: 'Bill'
в израза на заявката. Това ви казва, че документът вече е бил там, така че след това можете да разклоните логиката си, за да върнете „Грешка“ или какъвто отговор искате.
В другия случай може да е желателно да „не“ върнете „суровия“ отговор и вместо това да използвате върнат „монгустен документ“. В този случай променяме стойността да бъде new: false
без rawResult
опция.
User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
)
Повечето от същите неща се прилагат, с изключение на това, че сега действието е оригиналното състоянието на документа се връща за разлика от "модифицираното" състояние на документа "след" действието. Следователно, когато няма документ, който действително да съответства на оператора „query“, върнатият резултат е null
:
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null // <-- Got null in response :(
Това ви казва, че документът е „създаден“ и може да се спори, че вече знаете какво трябва да бъде съдържанието на документа, тъй като сте изпратили тези данни с израза (в идеалния случай в $setOnInsert
). Важното е, че вече знаете какво да върнете, „ако“ се нуждаете, за да върнете действително съдържанието на документа.
Обратно, „намерен“ документ връща „оригиналното състояние“, показвайки документа „преди“ да бъде модифициран:
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
Следователно всеки отговор, който не е null
" следователно е индикация, че документът вече е бил налице и отново можете да разклоните логиката си в зависимост от това какво всъщност е получено в отговор.
Така че това са двата основни подхода към това, което питате, и те със сигурност „вършат работа“! И точно както е демонстрирано и възпроизводимо със същите твърдения тук.
Добавка - Запазете дублиран ключ за лоши пароли
Има още един валиден подход, който също е намекнат в пълния списък, който по същество е просто .insert()
( или .create()
от mongoose models ) нови данни и имат хвърляне на грешка „дублиращ се ключ“, където всъщност се среща „уникалното“ свойство по индекс. Това е валиден подход, но има един конкретен случай на употреба при „проверка на потребителя“, който е удобна част от логическата обработка, и това е „проверка на пароли“.
Така че това е доста често срещан модел за извличане на потребителска информация чрез username
и password
комбинация. В случай на "upsert" тази комбинация се оправдава като "уникална" и следователно се прави опит за "вмъкване", ако не бъде намерено съвпадение. Точно това прави съпоставянето на паролата полезна реализация тук.
Имайте предвид следното:
// 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 }
);
При първия опит всъщност нямаме username
за "Fred"
, така че ще се случи „upsert“ и ще се случат всички други неща, както вече беше описано по-горе, за да се идентифицира дали това е създаване или намерен документ.
Изявлението, което следва, използва същото username
стойност, но предоставя различна от записаната парола. Тук MongoDB се опитва да „създаде“ новия документ, тъй като не съвпада с комбинацията, но защото username
се очаква да бъде "unique"
получавате „Грешка при дублиран ключ“:
{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }
Така че това, което трябва да осъзнаете, е, че сега получавате три условия за оценка за „безплатно“. Да бъдеш:
- „Upsert“ е записан или от
updatedExisting: false
илиnull
резултат в зависимост от метода. - Знаете, че документът (по комбинация) „съществува“ чрез
updatedExisting: true
или когато документът връща не еnull
". - Ако
password
предоставеното не съответства на това, което вече съществува заusername
, тогава ще получите „грешка с дублиращ се ключ“, която можете да прихванете и да отговорите по съответния начин, уведомявайки потребителя в отговор, че „паролата е неправилна“.
Всичко това отедно заявка.
Това е основната причина за използването на "upserts", за разлика от простото хвърляне на вмъквания в колекция, тъй като можете да получите различно разклоняване на логиката, без да правите допълнителни заявки към базата данни, за да определите "кое" от тези условия трябва да бъде действителният отговор.