MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Как да създадете елемент, ако не съществува и да върнете грешка, ако съществува

Както беше отбелязано в коментара по-рано, имате два основни подхода, за да разберете дали нещо е „създадено“ или не. Те са или към:

  • Върнете 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", за разлика от простото хвърляне на вмъквания в колекция, тъй като можете да получите различно разклоняване на логиката, без да правите допълнителни заявки към базата данни, за да определите "кое" от тези условия трябва да бъде действителният отговор.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Защо mongoose винаги добавя s в края на името на моята колекция

  2. Обединете с $sum в mongodb

  3. Валидирайте обекта спрямо схемата на Mongoose, без да записвате като нов документ

  4. Индексирането на масив/подобект в mongoDB причинява грешка при дублиране на ключ

  5. Какво означава h в документа oplog.rs?