Pendekatan untuk menangani ini tidak sederhana, karena mencampur "upserts" dengan menambahkan item ke "array" dapat dengan mudah menyebabkan hasil yang tidak diinginkan. Itu juga tergantung pada apakah Anda ingin logika menyetel bidang lain seperti "penghitung" yang menunjukkan berapa banyak kontak yang ada dalam larik, yang hanya ingin Anda tambah/kurangi saat item ditambahkan atau dihapus.
Namun dalam kasus yang paling sederhana, jika "kontak" hanya berisi nilai tunggal seperti ObjectId
menautkan ke koleksi lain, lalu $addToSet
pengubah berfungsi dengan baik, selama tidak ada "penghitung" yang terlibat:
Client.findOneAndUpdate(
{ "clientName": clientName },
{ "$addToSet": { "contacts": contact } },
{ "upsert": true, "new": true },
function(err,client) {
// handle here
}
);
Dan itu semua baik-baik saja karena Anda hanya menguji untuk melihat apakah dokumen cocok dengan "clientName", jika tidak, tambahkan. Apakah ada kecocokan atau tidak, $addToSet
operator akan menangani nilai "tunggal" yang unik, menjadi "objek" apa pun yang benar-benar unik.
Kesulitan datang di mana Anda memiliki sesuatu seperti:
{ "firstName": "John", "lastName": "Smith", "age": 37 }
Sudah ada di larik kontak, lalu Anda ingin melakukan sesuatu seperti ini:
{ "firstName": "John", "lastName": "Smith", "age": 38 }
Di mana maksud Anda sebenarnya adalah bahwa ini adalah John Smith yang "sama", dan hanya saja "usianya" tidak berbeda. Idealnya Anda hanya ingin "memperbarui" entri larik itu dan tidak membuat larik baru atau dokumen baru.
Bekerja dengan .findOneAndUpdate()
di mana Anda ingin mengembalikan dokumen yang diperbarui bisa jadi sulit. Jadi jika Anda tidak benar-benar menginginkan dokumen yang dimodifikasi sebagai tanggapan, maka API Operasi Massal
MongoDB dan driver inti sangat membantu di sini.
Mempertimbangkan pernyataan:
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
});
Ini bagus karena Operasi Massal di sini berarti bahwa semua pernyataan di sini dikirim ke server sekaligus dan hanya ada satu tanggapan. Perhatikan juga di sini bahwa logika di sini berarti bahwa paling banyak hanya dua operasi yang benar-benar akan mengubah apa pun.
Pada contoh pertama, $setOnInsert
pengubah memastikan bahwa tidak ada yang berubah ketika dokumen hanya cocok. Karena satu-satunya modifikasi di sini adalah dalam blok itu, ini hanya memengaruhi dokumen di mana "upsert" terjadi.
Perhatikan juga pada dua pernyataan berikutnya Anda tidak mencoba untuk "menyalahkan" lagi. Ini menganggap bahwa pernyataan pertama mungkin berhasil di tempat yang seharusnya, atau sebaliknya tidak masalah.
Alasan lain untuk tidak ada "upsert" adalah karena kondisi yang diperlukan untuk menguji keberadaan elemen dalam array akan mengarah ke "upsert" dari dokumen baru ketika mereka tidak terpenuhi. Itu tidak diinginkan, oleh karena itu tidak ada "upsert".
Apa yang mereka lakukan sebenarnya adalah masing-masing memeriksa apakah elemen array ada atau tidak, dan memperbarui elemen yang ada atau membuat yang baru. Oleh karena itu secara total, semua operasi berarti Anda memodifikasi "sekali" atau paling banyak "dua kali" jika terjadi upsert. Kemungkinan "dua kali" menciptakan overhead yang sangat sedikit dan tidak ada masalah nyata.
Juga dalam pernyataan ketiga $not
operator membalikkan logika $elemMatch
untuk menentukan bahwa tidak ada elemen larik dengan kondisi kueri.
Menerjemahkan ini dengan .findOneAndUpdate()
menjadi sedikit lebih dari masalah. Bukan hanya "keberhasilan" yang penting sekarang, tetapi juga menentukan bagaimana konten akhirnya dikembalikan.
Jadi ide terbaik di sini adalah menjalankan acara dalam "seri", dan kemudian mengerjakan sedikit keajaiban dengan hasilnya untuk mengembalikan formulir "diperbarui".
Bantuan yang akan kita gunakan di sini adalah dengan async.waterfall dan lodash perpustakaan:
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);
}
);
Itu mengikuti logika yang sama seperti sebelumnya karena hanya dua atau satu dari pernyataan itu yang benar-benar akan melakukan apa pun dengan kemungkinan bahwa dokumen "baru" yang dikembalikan akan menjadi null
. "Air terjun" di sini meneruskan hasil dari setiap tahap ke tahap berikutnya, termasuk akhir di mana juga kesalahan apa pun akan segera bercabang.
Dalam hal ini null
akan ditukar dengan objek kosong {}
dan _.merge()
metode akan menggabungkan dua objek menjadi satu, pada setiap tahap selanjutnya. Ini memberi Anda hasil akhir yang merupakan objek yang dimodifikasi, tidak peduli operasi sebelumnya mana yang benar-benar melakukan sesuatu.
Tentu saja, akan ada manipulasi berbeda yang diperlukan untuk $pull
, dan juga pertanyaan Anda memiliki data input sebagai bentuk objek itu sendiri. Tapi itu sebenarnya jawaban sendiri.
Setidaknya ini akan membantu Anda memulai cara mendekati pola pembaruan Anda.