2012-01-13 24 views
11

Tôi cần chèn tài liệu nếu nó không tồn tại. Tôi biết rằng tùy chọn "upsert" có thể làm điều đó, nhưng tôi có một số nhu cầu cụ thể.Upserts in mongodb khi sử dụng các giá trị _id tùy chỉnh

Trước tiên, tôi chỉ cần tạo tài liệu với trường _id của nó, nhưng chỉ khi nó chưa tồn tại. Trường _id của tôi là một số được tạo bởi tôi (không phải là ObjectId). Nếu tôi sử dụng tùy chọn "upsert" thì tôi nhận được "Mod on _id không được phép"

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

Tôi biết rằng chúng tôi không thể sử dụng _id trong bộ $.

Vì vậy, câu hỏi của tôi là: Nếu có cách nào để tạo "nếu không tồn tại" một cách nguyên tử trong mongodb?

EDIT: Theo đề xuất của @Barrie làm việc này (sử dụng nodejs và cầy mangut):

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

Nhưng tôi vẫn tự hỏi nếu nó là cách tốt nhất để làm điều đó.

+0

cố gắng sử dụng thao tác 'lưu'? tức là db.collection.save ({"_ id": your_id}) –

+0

Lưu lỗi với lỗi khóa trùng lặp nếu đã tồn tại – aartiles

Trả lời

5

Bạn chỉ có thể sử dụng insert(). Nếu tài liệu với _id mà bạn chỉ định đã tồn tại, thì insert() sẽ thất bại, không có gì sẽ được sửa đổi - vì vậy "tạo nếu nó không tồn tại" là những gì nó đã làm theo mặc định khi bạn sử dụng insert() với người dùng- đã tạo _id.

+0

Làm cho tinh thần, tôi đang thử nó. – aartiles

+2

Điều này vẫn không xử lý điều kiện chủng tộc nơi tài liệu bị xóa sau khi chèn và trước bản cập nhật tiếp theo. – Lucian

22

Tôi có cùng một vấn đề, nhưng tìm thấy giải pháp tốt hơn cho nhu cầu của tôi. Bạn có thể sử dụng cùng một kiểu truy vấn đó nếu bạn chỉ cần xóa thuộc tính _id khỏi đối tượng cập nhật. Vì vậy, nếu lúc đầu bạn nhận được một lỗi với điều này:

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

thay vì sử dụng này:

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

Điều này là tốt vì nó hoạt động cho cả chèn và cập nhật.

+1

Tính năng này có hoạt động với _id không phải là một ObjectID, như được mô tả trong câu hỏi không? –

+1

Có. Nó gây ra lỗi 'Mod on _id not allowed' hoặc với _id là một ObjectID hoặc một giá trị tùy chỉnh. Vì vậy, bạn luôn phải xóa _id khỏi đối tượng '$ set'. –

+0

Cảm ơn, Xóa _id khỏi đối tượng $ set làm việc cho tôi. – ATilara

3

CẬP NHẬT: Upsert với _id có thể được thực hiện mà không cần $setOnInsert, như được giải thích bởi @Barrie ở trên.

Bí quyết là sử dụng $setOnInsert:{_id:1} với upsert, theo cách đó _id chỉ được viết nếu đó là phần chèn và không bao giờ cập nhật.

Only, there was a bug preventing this from working until v2.6 - Tôi vừa thử trên 2,4 và không hoạt động.

Cách giải quyết mà tôi sử dụng là có một trường ID khác có chỉ mục duy nhất. Ví dụ. $setOnInsert:{myId:1}.

+1

setOnInsert đã làm việc cho tôi. trong khi câu trả lời của Barrie, chèn trực tiếp sẽ không làm việc cập nhật; Câu trả lời của Nate Barr, cập nhật bởi _id giới hạn tiêu chí tìm kiếm. Những gì tôi muốn làm là: db.coll.update ({a: 1, b: 2}, {$ set: {c: 3, d: 4}, $ setOnInsert: {_id: 'xxxx'}}, { upsert: true}) – vdonkey

0

Xin lưu ý rằng $ setOnInsert không hoạt động dễ dàng khi bạn tăng một đối tượng giá trị => giá trị đơn giản (không phải là $ set hoặc khác). Tôi cần sử dụng (trong 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); 
} 
Các vấn đề liên quan