2012-01-13 14 views
11

È necessario inserire un documento se non esiste. So che l'opzione "upsert" può farlo, ma ho alcune esigenze particolari.Upserts in mongodb quando si utilizzano valori _id personalizzati

Prima devo creare il documento solo con il suo campo _id, ma solo se non esiste già. Il mio campo _id è un numero generato da me (non un ObjectId). Se si utilizza l'opzione "upsert" allora ottengo "Mod su _id non ammesso"

db.mycollection.update({ _id: id }, { _id: id }, { upsert: true }); 

So che non possiamo usare il _id in un $ set.

Quindi, la mia domanda è: se esiste un modo per un "creare se non esiste" atomicamente in mongodb?

EDIT: Come proposto dalla @Barrie questo funziona (usando nodejs e mangusta):

var newUser = new User({ _id: id }); 
newUser.save(function (err) {    
    if (err && err.code === 11000) {    
      console.log('If duplicate key the user already exists', newTwitterUser); 
     return; 
    } 
    console.log('New user or err', newTwitterUser); 
}); 

Ma mi chiedo ancora se è il modo migliore per farlo.

+0

provare ad utilizzare un'operazione 'save'? Ad esempio, db.collection.save ({"_ id": your_id}) –

+0

Salva fallito con un errore chiave duplicato se esiste già – aartiles

risposta

5

È possibile utilizzare solo insert(). Se il documento con _id specificato esiste già, l'insert() fallirà, nulla verrà modificato - quindi "crea se non esiste" è ciò che sta già facendo di default quando usi insert() con un utente- creato _id.

+0

Ha senso, lo sto provando. – aartiles

+2

Questo ancora non gestisce la condizione di competizione in cui il documento viene eliminato dopo l'inserimento e prima dell'aggiornamento successivo. – Lucian

22

Ho avuto lo stesso problema, ma ho trovato una soluzione migliore per le mie esigenze. È possibile utilizzare lo stesso stile di query se si rimuove semplicemente l'attributo _id dall'oggetto di aggiornamento. Quindi, se in un primo momento si ottiene un errore con questo:

db.mycollection.update({ _id: id }, {$set: { _id: id, name: 'name' }}, { upsert: true }); 

invece usare questo:

db.mycollection.update({ _id: id }, {$set: { name: 'name' }}, { upsert: true }); 

Questo è meglio perché funziona sia per inserimento e aggiornamento.

+1

Funziona con un _id che non è un ObjectID, come descritto nella domanda? –

+1

Sì. Causa l'errore "Mod su _id non consentito" con _id come ObjectID o un valore personalizzato. Quindi devi sempre rimuovere _id dall'oggetto '$ set'. –

+0

Grazie, Rimuovere _id dall'oggetto $ set funziona per me. – ATilara

3

UPDATE: Upsert con _id può essere eseguito senza $setOnInsert, come spiegato da @Barrie sopra.

Il trucco è utilizzare $setOnInsert:{_id:1} con upsert, in modo che _id venga scritto solo se si tratta di un inserto e mai di aggiornamenti.

Only, there was a bug preventing this from working until v2.6 - Ho appena provato su 2.4 e non funziona.

La soluzione che utilizzo è avere un altro campo ID con un indice univoco. Per esempio. $setOnInsert:{myId:1}.

+1

setOnInsert ha funzionato per me. mentre la risposta di Barrie, inserendo direttamente non farà l'aggiornamento funziona; La risposta di Nate Barr, l'aggiornamento di _id ha limitato i criteri di ricerca. Quello che voglio fare è: db.coll.update ({a: 1, b: 2}, {$ set: {c: 3, d: 4}, $ setOnInsert: {_id: 'xxxx'}}, { upsert: true}) – vdonkey

0

Si noti che $ setOnInsert non funziona facilmente quando si esegue il push di una chiave semplice => oggetto valore (non $ set o altro). Ho bisogno di usarlo (in PHP):

public function update($criteria , $new_object, array $options = array()){ 
    // In 2.6, $setOnInsert with upsert == true work with _id field 
    if(isset($options['upsert']) && $options['upsert']){ 
     $firstKey = array_keys($new_object)[0]; 
     if(strpos($firstKey, '$')===0){ 
      $new_object['$setOnInsert']['_id'] = $this->getStringId(); 
     } 
     //Even, we need to check if the object exists 
     else if($this->findOne($criteria, ['_id'])===null){ 
      //In this case, we need to set the _id 
      $new_object['_id'] = $this->getStringId(); 
     } 

    } 
    return parent::update($criteria, $new_object, $options); 
} 
Problemi correlati