2012-01-13 17 views
11

Necesito insertar un documento si no existe. Sé que la opción "upsert" puede hacer eso, pero tengo algunas necesidades particulares.Responde en mongodb cuando se usan valores de _id personalizados

Primero necesito crear el documento con su campo _id solamente, pero solo si no existe ya. Mi campo _id es un número generado por mí (no un ObjectId). Si utilizo la opción "upsert" Entonces consigo "Mod en _id no permitido"

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

Sé que no podemos usar _ID en un conjunto $.

Entonces, mi pregunta es: ¿hay alguna manera de "crear si no existe" atómicamente en mongodb?

EDIT: Según lo propuesto por @Barrie esto funciona (usando nodejs y mangostas):

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

Pero todavía me pregunto si es la mejor manera de hacerlo.

+0

intente utilizar una operación 'save'? es decir db.collection.save ({"_ id": your_id}) –

+0

Guardar falla con un error de clave duplicada si ya existe – aartiles

Respuesta

5

Solo puede usar insert(). Si el documento con el _id que usted especifica ya existe, el inserto() fallará, nada será modificado, de modo que "crear si no existe" es lo que ya está haciendo de manera predeterminada cuando usa insert() con un usuario- creado _id.

+0

Tiene sentido, lo estoy intentando. – aartiles

+2

Esto todavía no maneja la condición de carrera donde el documento se elimina después de la inserción y antes de la actualización posterior. – Lucian

22

Tuve el mismo problema, pero encontré una solución mejor para mis necesidades. Puede usar ese mismo estilo de consulta si simplemente elimina el atributo _id del objeto de actualización. Así que si en un primer momento se produce un error con esto:

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

en lugar de utilizar esto:

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

Esto es mejor, ya que funciona tanto para la inserción y actualización.

+1

¿Funciona esto con un _id que no es un ObjectID, como se describe en la pregunta? –

+1

Sí. Causa el error 'Mod en _id no permitido' con _id como un ObjectID o un valor personalizado. Por lo tanto, siempre debe eliminar _id del objeto '$ set'. –

+0

Gracias, Eliminar _id del objeto $ set funciona para mí. – ATilara

3

ACTUALIZACIÓN: Upsert con _id se puede hacer sin $setOnInsert, como explica por @Barrie anterior.

El truco es usar $setOnInsert:{_id:1} con upsert, de esa manera el _id solo se escribe en si es una inserción, y nunca para las actualizaciones.

Only, there was a bug preventing this from working until v2.6 - Acabo de probarlo en 2.4 y no está funcionando.

La solución alternativa que uso es tener otro campo de ID con un índice único. P.ej. $setOnInsert:{myId:1}.

+1

setOnInsert funcionó para mí. mientras que la respuesta de Barrie, insertar directamente no hará que la actualización funcione; La respuesta de Nate Barr, la actualización por _id limitó los criterios de búsqueda. Lo que quiero hacer es: db.coll.update ({a: 1, b: 2}, {$ set: {c: 3, d: 4}, $ setOnInsert: {_id: 'xxxx'}}, { upsert: true}) – vdonkey

0

Tenga en cuenta que $ setOnInsert no funciona fácilmente cuando se restaura un objeto simple key => value (no $ set u otro). Necesito usar eso (en 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); 
} 
Cuestiones relacionadas