Cách tiếp cận để xử lý điều này không phải là một cách đơn giản, vì việc trộn các "cảnh báo" với việc thêm các mục vào "mảng" có thể dễ dàng dẫn đến kết quả không mong muốn. Nó cũng phụ thuộc vào việc bạn muốn logic để đặt các trường khác như "bộ đếm" cho biết có bao nhiêu địa chỉ liên hệ trong một mảng mà bạn chỉ muốn tăng / giảm khi các mục được thêm vào hoặc xóa tương ứng.
Tuy nhiên, trong trường hợp đơn giản nhất, nếu "địa chỉ liên hệ" chỉ chứa một giá trị đơn lẻ chẳng hạn như ObjectId
liên kết đến một bộ sưu tập khác, sau đó đến $ addToSet
công cụ sửa đổi hoạt động tốt, miễn là không có "bộ đếm" nào liên quan:
Client.findOneAndUpdate(
{ "clientName": clientName },
{ "$addToSet": { "contacts": contact } },
{ "upsert": true, "new": true },
function(err,client) {
// handle here
}
);
Và điều đó hoàn toàn ổn vì bạn chỉ đang thử nghiệm để xem liệu một doucment có khớp trên "clientName" hay không, nếu không nâng cấp nó. Cho dù có khớp hay không, thì $ addToSet
toán tử sẽ quan tâm đến các giá trị "số ít" duy nhất, là bất kỳ "đối tượng" nào thực sự là duy nhất.
Khó khăn đến ở chỗ bạn gặp phải những thứ như:
{ "firstName": "John", "lastName": "Smith", "age": 37 }
Đã có trong mảng danh bạ, và sau đó bạn muốn làm điều gì đó như sau:
{ "firstName": "John", "lastName": "Smith", "age": 38 }
Ý định thực sự của bạn là đây là John Smith "giống", và chỉ là "tuổi" không khác. Lý tưởng nhất là bạn chỉ muốn "cập nhật" rằng neiter kết thúc mục nhập mảng tạo một mảng mới hoặc một tài liệu mới.
Làm việc này với .findOneAndUpdate ()
nơi bạn muốn tài liệu cập nhật trở lại có thể khó khăn. Vì vậy, nếu bạn không thực sự muốn tài liệu được sửa đổi theo phản hồi, thì hãy API hoạt động hàng loạt
của MongoDB và trình điều khiển cốt lõi giúp ích nhiều nhất ở đây.
Xem xét các tuyên bố:
var bulk = Client.collection.initializeOrderedBulkOP();
// First try the upsert and set the array
bulk.find({ "clientName": clientName }).upsert().updateOne({
"$setOnInsert": {
// other valid client info in here
"contacts": [contact]
}
});
// Try to set the array where it exists
bulk.find({
"clientName": clientName,
"contacts": {
"$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}
}
}).updateOne({
"$set": { "contacts.$": contact }
});
// Try to "push" the array where it does not exist
bulk.find({
"clientName": clientName,
"contacts": {
"$not": { "$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}}
}
}).updateOne({
"$push": { "contacts": contact }
});
bulk.execute(function(err,response) {
// handle in here
});
Điều này rất hay vì Hoạt động hàng loạt ở đây có nghĩa là tất cả các câu lệnh ở đây được gửi đến máy chủ cùng một lúc và chỉ có một phản hồi. Cũng lưu ý ở đây rằng logic có nghĩa là ở đây chỉ có tối đa hai hoạt động sẽ thực sự sửa đổi bất kỳ điều gì.
Trong trường hợp đầu tiên, $ setOnInsert
công cụ sửa đổi đảm bảo rằng không có gì bị thay đổi khi tài liệu chỉ là một đối sánh. Vì các sửa đổi duy nhất ở đây nằm trong khối đó, điều này chỉ ảnh hưởng đến tài liệu có "upert" xảy ra.
Cũng lưu ý ở hai câu tiếp theo bạn không cố gắng "upert" nữa. Điều này cho rằng tuyên bố đầu tiên có thể thành công ở nơi nó phải có, hoặc không quan trọng.
Lý do khác để không có "upert" ở đó là vì các điều kiện cần thiết để kiểm tra sự hiện diện của phần tử trong mảng sẽ dẫn đến việc "upert" một tài liệu mới khi chúng không được đáp ứng. Điều đó không được mong muốn, do đó không có "tăng cường".
Trên thực tế, những gì họ làm là kiểm tra xem phần tử mảng có hiện diện hay không, và cập nhật phần tử hiện có hoặc tạo một phần tử mới. Do đó, tất cả các thao tác có nghĩa là bạn sửa đổi "một lần" hoặc nhiều nhất là "hai lần" trong trường hợp xảy ra sự cố nâng cấp. "Hai lần" có thể tạo ra rất ít chi phí và không có vấn đề thực sự.
Cũng trong câu lệnh thứ ba, $ not
toán tử đảo ngược logic của $ elemMatch
để xác định rằng không tồn tại phần tử mảng nào với điều kiện truy vấn.
Dịch điều này bằng .findOneAndUpdate ()
trở thành một vấn đề nhiều hơn một chút. Bây giờ không chỉ là "thành công" mà nó còn xác định cách nội dung cuối cùng được trả về.
Vì vậy, ý tưởng tốt nhất ở đây là chạy các sự kiện trong "chuỗi" và sau đó thực hiện một chút phép thuật với kết quả để trả về biểu mẫu "đã cập nhật" cuối cùng.
Trợ giúp mà chúng tôi sẽ sử dụng ở đây là cả async.waterfall và lodash thư viện:
var _ = require('lodash'); // letting you know where _ is coming from
async.waterfall(
[
function(callback) {
Client.findOneAndUpdate(
{ "clientName": clientName },
{
"$setOnInsert": {
// other valid client info in here
"contacts": [contact]
}
},
{ "upsert": true, "new": true },
callback
);
},
function(client,callback) {
Client.findOneAndUpdate(
{
"clientName": clientName,
"contacts": {
"$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}
}
},
{ "$set": { "contacts.$": contact } },
{ "new": true },
function(err,newClient) {
client = client || {};
newClient = newClient || {};
client = _.merge(client,newClient);
callback(err,client);
}
);
},
function(client,callback) {
Client.findOneAndUpdate(
{
"clientName": clientName,
"contacts": {
"$not": { "$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}}
}
},
{ "$push": { "contacts": contact } },
{ "new": true },
function(err,newClient) {
newClient = newClient || {};
client = _.merge(client,newClient);
callback(err,client);
}
);
}
],
function(err,client) {
if (err) throw err;
console.log(client);
}
);
Điều đó tuân theo cùng một logic như trước đó là chỉ có hai hoặc một trong những câu lệnh đó thực sự sẽ làm bất cứ điều gì với khả năng tài liệu "mới" được trả về sẽ là null
. "Thác nước" ở đây chuyển kết quả từ mỗi giai đoạn sang giai đoạn tiếp theo, bao gồm cả phần cuối, nơi bất kỳ lỗi nào cũng sẽ ngay lập tức phân nhánh.
Trong trường hợp này, null
sẽ được hoán đổi cho một đối tượng trống {}
và _.merge ()
phương thức sẽ kết hợp hai đối tượng thành một, ở mỗi giai đoạn sau. Điều này cung cấp cho bạn kết quả cuối cùng là đối tượng được sửa đổi, bất kể hoạt động trước đó thực sự đã làm gì.
Tất nhiên, sẽ có một thao tác khác được yêu cầu đối với $ pull
, và câu hỏi của bạn cũng có dữ liệu đầu vào là một dạng đối tượng. Nhưng đó thực sự là câu trả lời.
Điều này ít nhất sẽ giúp bạn bắt đầu về cách tiếp cận mẫu cập nhật của mình.