Восстанавливает mongodb при использовании пользовательских значений _id

Мне нужно вставить документ, если он не существует. Я знаю, что опция "upsert" может это сделать, но у меня есть определенные потребности.

Сначала мне нужно создать документ только с его полем _id, но только если он еще не существует. Мое поле _id - это число, генерируемое мной (не ObjectId). Если я использую параметр "upsert", тогда я получаю "Mod on _id not allowed"

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

Я знаю, что мы не можем использовать _id в $set.

Итак, мой вопрос: если какой-либо способ "создать, если не существует" атомарно в mongodb?

EDIT: Как было предложено @Barrie, это работает (используя nodejs и mongoose):

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

Но я все еще думаю, что это лучший способ сделать это.

Ответ 1

Вы можете просто использовать insert(). Если документ с указанным вами _id уже существует, вставка() завершится с ошибкой, ничего не будет изменено - так что "создать, если он не существует" - это то, что он уже делает по умолчанию, когда вы используете insert() созданный _id.

Ответ 2

У меня была та же проблема, но я нашел лучшее решение для своих нужд. Вы можете использовать тот же стиль запроса, если просто удалите атрибут _id из объекта обновления. Поэтому, если сначала вы получите сообщение об ошибке:

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

вместо этого используйте это:

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

Это лучше, потому что оно работает как для вставки, так и для обновления.

Ответ 3

UPDATE: Upsert с помощью _id можно выполнить без $setOnInsert, как описано выше @Barrie.

Хитрость заключается в использовании $setOnInsert:{_id:1} с upsert, таким образом, _id записывается только в том случае, если он вставляется и никогда не обновляется.

Только, была ошибка, препятствующая этому работать до версии v2.6 - я просто попробовал его на 2.4 и не работал.

Обходной путь, который я использую, имеет другое поле ID с уникальным индексом. Например. $setOnInsert:{myId:1}.

Ответ 4

Обратите внимание, что $setOnInsert не работает легко, когда вы запускаете простой объект key = > value (а не $set или другой). Мне нужно использовать это (в 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);
}