sql >> Databasteknik >  >> NoSQL >> MongoDB

Hur man skapar objekt om det inte finns och returnerar ett fel om det finns

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 kontrollera updatedExisting 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 eller null resultat beroende på metoden.
  • Du vet att dokumentet (genom kombinationen) "finns" via antingen updatedExisting: true eller där dokumentet returnerar var "inte null ".
  • Om password tillhandahållet matchade inte det som redan fanns för username , 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.



  1. Dynamisk databasanslutning till mongodb eller mongoose från nodejs

  2. Hur gör man en OCH-fråga på en array i mongodb?

  3. Redis köarbetare kraschar i utcparse

  4. NodeJS, Mongoose:Hur man får relaterad data med hjälp av mongoose