Восстанавливает 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);
}