sql >> Database teknologi >  >> NoSQL >> MongoDB

Sådan opretter du en vare, hvis den ikke findes, og returnerer en fejl, hvis den findes

Som nævnt i kommentaren tidligere, har du to grundlæggende tilgange til at finde ud af, om noget blev "skabt" eller ej. Disse er enten til:

  • Returner rawResult i svaret og tjek updatedExisting egenskab, som fortæller dig, om det er en "upsert" eller ej

  • Indstil new: false så "intet dokument" faktisk returneres som resultat, når det faktisk er en "upsert"

Som en liste for at demonstrere:

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()
  }


})()

Og outputtet:

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ørste tilfælde betragter faktisk denne kode:

User.findOneAndUpdate(
  { username: 'Bill' },
  { $setOnInsert: { password: 'password' } },
  { upsert: true, new: true, rawResult: true }
)

De fleste muligheder er standard her som "alle" "upsert" handlinger vil resultere i, at feltindholdet bliver brugt til at "matche" (dvs. username ) er "altid" oprettet i det nye dokument, så du behøver ikke $set det felt. For faktisk ikke at "ændre" andre felter på efterfølgende anmodninger kan du bruge $setOnInsert , som kun tilføjer disse egenskaber under en "upsert" handling, hvor der ikke findes noget match.

Her er standarden new: true bruges til at returnere det "modificerede" dokument fra handlingen, men forskellen ligger i rawResult som vist i det returnerede svar:

{
  "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
    }
  }
}

I stedet for et "mongoose-dokument" får du det faktiske "rå" svar fra chaufføren. Det faktiske dokumentindhold er under "value" egenskab, men det er "lastErrorObject" vi er interesserede i.

Her ser vi egenskaben updatedExisting: false . Dette indikerer, at "ingen match" faktisk blev fundet, hvorfor et nyt dokument blev "oprettet". Så du kan bruge dette til at fastslå, at oprettelsen faktisk skete.

Når du udsteder de samme forespørgselsindstillinger igen, vil resultatet være anderledes:

{
  "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ærdien er nu true , og det skyldes, at der allerede var et dokument, der matchede username: 'Bill' i forespørgselserklæringen. Dette fortæller dig, at dokumentet allerede var der, så du kan derefter forgrene din logik for at returnere en "fejl" eller et hvilket som helst svar, du ønsker.

I det andet tilfælde kan det være ønskeligt at "ikke" returnere det "rå" svar og i stedet bruge et returneret "mongoose-dokument". I dette tilfælde ændrer vi værdien til at være new: false uden rawResult mulighed.

User.findOneAndUpdate(
  { username: 'Ted' },
  { $setOnInsert: { password: 'password' } },
  { upsert: true, new: false }
)

De fleste af de samme ting gælder bortset fra, at nu er handlingen den originale dokumentets tilstand returneres i modsætning til dokumentets "ændrede" tilstand "efter" handlingen. Derfor er det returnerede resultat null, når der ikke er noget dokument, der faktisk matcher "query"-sætningen :

Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null           // <-- Got null in response :(

Dette fortæller dig, at dokumentet blev "oprettet", og det kan diskuteres, at du allerede ved, hvad indholdet af dokumentet skal være, siden du sendte disse data med erklæringen (ideelt set i $setOnInsert ). Pointen er, at du allerede ved, hvad du skal returnere "skulle" du har brug for for rent faktisk at returnere dokumentindholdet.

I modsætning hertil returnerer et "fundet" dokument den "oprindelige tilstand", der viser dokumentet "før" det blev ændret:

{
  "_id": "5adfc8696878cfc4992e7639",
  "username": "Ted",
  "__v": 0,
  "password": "password"
}

Derfor ethvert svar, der ikke er null " er derfor en indikation af, at dokumentet allerede var til stede, og igen kan du forgrene din logik afhængigt af, hvad der faktisk blev modtaget som svar.

Så det er de to grundlæggende tilgange til det, du spørger om, og de "virker helt sikkert"! Og lige som det er demonstreret og reproducerbart med de samme udsagn her.

Tillæg - Reserver duplikatnøgle til dårlige adgangskoder

Der er en mere gyldig tilgang, som også er antydet i den fulde liste, som i det væsentlige er at blot .insert() ( eller .create() fra mongoose-modeller ) nye data og har et "duplicate key"-fejlkast, hvor den "unikke" egenskab efter indeks faktisk stødes på. Det er en gyldig tilgang, men der er et særligt tilfælde i "brugervalidering", som er et praktisk stykke logisk håndtering, og det er "validering af adgangskoder".

Så det er et ret almindeligt mønster at hente brugeroplysninger ved hjælp af username og password kombination. I tilfælde af en "upsert" retfærdiggør denne kombination som "unik", og derfor forsøges en "insert", hvis der ikke findes noget match. Det er præcis det, der gør matchning af adgangskoden til en nyttig implementering her.

Overvej følgende:

    // 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 }
    );

Ved første forsøg har vi faktisk ikke et username for "Fred" , så "upsert" ville forekomme, og alle de andre ting, som allerede er beskrevet ovenfor, sker for at identificere, om det var en oprettelse eller et fundet dokument.

Udsagnet, der følger, bruger det samme username værdi, men giver en anden adgangskode end den, der er registreret. Her forsøger MongoDB at "oprette" det nye dokument, da det ikke matchede på kombinationen, men fordi username forventes at være "unique" du modtager en "Duplicate key error":

{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }

Så hvad du bør indse er, at du nu får tre betingelser for at evaluere "gratis". At være:

  • "Upsert" blev registreret af enten updatedExisting: false eller null resultat afhængigt af metoden.
  • Du ved, at dokumentet (ved kombination) "eksisterer" via enten updatedExisting: true eller hvor dokumentet returnerer var "ikke null ".
  • Hvis password forudsat stemte ikke overens med det, der allerede eksisterede for username , så får du "duplikatnøglefejlen", som du kan fange og svare i overensstemmelse hermed, og som svar rådgive brugeren om, at "adgangskoden er forkert".

Alt det fra én anmodning.

Det er hovedårsagen til at bruge "upserts" i modsætning til blot at smide inserts på en samling, da du kan få en anden forgrening af logikken uden at foretage yderligere anmodninger til databasen for at bestemme "hvilken" af disse betingelser der skal være det faktiske svar.



  1. C#/.NET-klient til Redis

  2. Forbind R til fjernbetjening mongoDB med rmongodb

  3. Hvordan implementerer man ASP.NET Core 3.1 Identity med MongoDB?

  4. Hvordan installerer jeg mongodb på beaglebone black