MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Jak utworzyć element, jeśli nie istnieje i zwrócić błąd, jeśli istnieje?

Jak zauważono we wcześniejszym komentarzu, masz dwa podstawowe podejścia do ustalenia, czy coś zostało „stworzone”, czy nie. Są to:

  • Zwróć rawResult w odpowiedzi i sprawdź updatedExisting właściwość, która mówi Ci, czy jest to „przesadne”, czy nie

  • Ustaw new: false tak, że "brak dokumentu" jest faktycznie zwracany w wyniku, gdy w rzeczywistości jest to "upsert"

Jako wykaz do zademonstrowania:

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


})()

A wynik:

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

Tak więc pierwszy przypadek dotyczy tego kodu:

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

Większość opcji jest tutaj standardowa jako „wszystkie” "upsert" akcje spowodują, że zawartość pola zostanie użyta do „dopasowania” (tj. username ) to „zawsze” utworzone w nowym dokumencie, więc nie musisz $set to pole. Aby faktycznie nie „modyfikować” innych pól w kolejnych żądaniach, możesz użyć $setOnInsert , który dodaje te właściwości tylko podczas "upsert" akcja, w której nie znaleziono dopasowania.

Tutaj standardowe new: true służy do zwrócenia "zmodyfikowanego" dokumentu z akcji, ale różnica polega na rawResult jak pokazano w zwróconej odpowiedzi:

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

Zamiast „dokumentu mangusty” otrzymujesz rzeczywistą „surową” odpowiedź od kierowcy. Rzeczywista treść dokumentu znajduje się pod "value" właściwość, ale jest to "lastErrorObject" jesteśmy zainteresowani.

Tutaj widzimy właściwość updatedExisting: false . Oznacza to, że faktycznie znaleziono „brak dopasowania”, a zatem „utworzono” nowy dokument. Możesz więc użyć tego do ustalenia, że ​​tworzenie rzeczywiście miało miejsce.

Po ponownym wprowadzeniu tych samych opcji zapytania wynik będzie inny:

{
  "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 wartość jest teraz true , a to dlatego, że istniał już dokument pasujący do username: 'Bill' w zapytaniu. Oznacza to, że dokument już tam był, więc możesz rozgałęzić swoją logikę, aby zwrócić "Błąd" lub dowolną odpowiedź.

W innym przypadku może być pożądane, aby „nie” zwracać „surowej” odpowiedzi i zamiast tego użyć zwróconego „dokumentu mangusty”. W tym przypadku zmieniamy wartość na new: false bez rawResult opcja.

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

Ma zastosowanie większość tych samych rzeczy, z wyjątkiem tego, że teraz akcja jest oryginalna zwracany jest stan dokumentu w przeciwieństwie do „zmodyfikowanego” stanu dokumentu „po” akcji. Dlatego gdy nie ma dokumentu, który faktycznie pasuje do instrukcji "zapytanie", zwracany wynik to null :

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

To mówi, że dokument został "utworzony" i można argumentować, że już wiesz, jaka powinna być zawartość dokumentu, ponieważ wysłałeś te dane za pomocą instrukcji (najlepiej w $setOnInsert ). Chodzi o to, że już wiesz, co zwrócić „jeśli” potrzebujesz, aby faktycznie zwrócić zawartość dokumentu.

Z kolei „znaleziony” dokument zwraca „pierwotny stan” dokumentu „przed” jego modyfikacją:

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

Dlatego każda odpowiedź, która nie jest null ” jest zatem wskazówką, że dokument był już obecny i ponownie możesz rozgałęzić swoją logikę w zależności od tego, co faktycznie otrzymano w odpowiedzi.

Więc to są dwa podstawowe podejścia do tego, o co prosisz, i na pewno „działają”! I tak jak jest to pokazane i powtarzalne za pomocą tych samych stwierdzeń tutaj.

Uzupełnienie – Rezerwuj zduplikowany klucz w przypadku złych haseł

Jest jeszcze jedno ważne podejście, o którym wspomniano w pełnej liście, a mianowicie po prostu .insert() ( lub .create() z modeli mongoose ) nowe dane i zgłaszany jest błąd „duplikatu klucza”, w którym faktycznie napotkano „unikalną” właściwość indeksu. Jest to prawidłowe podejście, ale istnieje jeden konkretny przypadek użycia w „walidacji użytkowników”, który jest przydatnym elementem obsługi logiki, a jest nim „walidacja haseł”.

Jest to więc dość powszechny wzorzec pobierania informacji o użytkowniku przez username i password połączenie. W przypadku „upsert” ta kombinacja uzasadnia jako „unikalna” i dlatego próba „insert” jest podejmowana, jeśli nie zostanie znalezione dopasowanie. Właśnie to sprawia, że ​​dopasowanie hasła jest tutaj przydatną implementacją.

Rozważ następujące kwestie:

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

Przy pierwszej próbie tak naprawdę nie mamy username dla "Fred" , więc nastąpi "upsert" i wszystkie inne rzeczy, jak już opisano powyżej, będą identyfikować, czy był to twór, czy znaleziony dokument.

Poniższa instrukcja używa tej samej username wartość, ale zapewnia inne hasło niż to, co jest rejestrowane. Tutaj MongoDB próbuje "utworzyć" nowy dokument, ponieważ nie pasuje on do kombinacji, ale ponieważ username oczekuje się, że będzie "unique" otrzymujesz komunikat „Błąd zduplikowanego klucza”:

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

Więc powinieneś zdać sobie sprawę, że teraz masz trzy warunki do oceny „za darmo”. Istota:

  • Zmiana „upsert” została zarejestrowana przez updatedExisting: false lub null wynik w zależności od metody.
  • Wiesz, że dokument ( przez kombinację ) „istnieje” za pomocą updatedExisting: true lub gdzie zwracany dokument był „nie null ".
  • Jeśli password podane nie było zgodne z tym, co już istniało dla username , otrzymasz „błąd zduplikowanego klucza”, który możesz przechwycić i odpowiednio zareagować, informując użytkownika, że ​​„hasło jest nieprawidłowe”.

Wszystko to od jednego żądanie.

To jest główny powód używania „upserts” w przeciwieństwie do zwykłego wrzucania wstawek do kolekcji, ponieważ możesz uzyskać różne rozgałęzienia logiki bez wysyłania dodatkowych żądań do bazy danych w celu określenia, „który” z tych warunków powinien być rzeczywistą odpowiedzią.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Używasz mongoimport do wczytywania CSV do zagnieżdżonej struktury?

  2. mongodb - sprawdź czy pole jest jedną z wielu wartości

  3. Synchronizuj MongoDB przez ssh

  4. Wyświetlanie listy użytkowników dla określonej bazy danych za pomocą PyMongo

  5. kopia mongo z jednej kolekcji do drugiej (na tej samej bazie danych)