Fremgangsmåden til at håndtere dette er ikke enkel, da blanding af "upserts" med tilføjelse af elementer til "arrays" nemt kan føre til uønskede resultater. Det afhænger også af, om du vil have logik til at indstille andre felter, såsom en "tæller", der angiver, hvor mange kontakter der er inden for et array, som du kun ønsker at øge/mindske, efterhånden som elementer tilføjes eller fjernes.
I det mest simple tilfælde, hvis "kontaktpersonerne" kun indeholdt en enkelt værdi, såsom en ObjectId
linker til en anden samling, derefter $addToSet
modifier fungerer godt, så længe der ingen "tællere" er involveret:
Client.findOneAndUpdate(
{ "clientName": clientName },
{ "$addToSet": { "contacts": contact } },
{ "upsert": true, "new": true },
function(err,client) {
// handle here
}
);
Og det er alt i orden, da du kun tester for at se, om et dokument matcher på "klientnavnet", hvis ikke forryk det. Uanset om der er et match eller ej, er $addToSet
operatør vil tage sig af unikke "singulære" værdier, idet de er ethvert "objekt", der er virkelig unikt.
Vanskelighederne kommer ind, hvor du har noget som:
{ "firstName": "John", "lastName": "Smith", "age": 37 }
Allerede i kontaktgruppen, og så vil du gøre noget som dette:
{ "firstName": "John", "lastName": "Smith", "age": 38 }
Hvor din egentlige hensigt er, at dette er den "samme" John Smith, og det er bare, at "alderen" ikke er anderledes. Ideelt set vil du bare "opdatere" denne array-indgang og ikke oprette en ny matrix eller et nyt dokument.
Arbejder dette med .findOneAndUpdate()
hvor du vil have det opdaterede dokument tilbage, kan være svært. Så hvis du ikke rigtig vil have det ændrede dokument som svar, så Bulk Operations API
af MongoDB og kernedriveren er til størst hjælp her.
I betragtning af udsagnene:
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
});
Dette er rart, da Bulk Operations her betyder, at alle erklæringer her sendes til serveren på én gang, og der kun er ét svar. Bemærk også her, at logikken her betyder, at højst kun to operationer rent faktisk vil ændre noget.
I første omgang er $setOnInsert
modifier sørger for, at intet ændres, når dokumentet kun er et match. Da de eneste ændringer her er inden for den blok, påvirker dette kun et dokument, hvor der forekommer en "upsert".
Bemærk også på de næste to udsagn, at du ikke forsøger at "ophæve" igen. Dette mener, at den første udtalelse muligvis var vellykket, hvor den skulle være, eller på anden måde var ligegyldig.
Den anden grund til ingen "upsert" er, fordi de nødvendige betingelser for at teste tilstedeværelsen af elementet i arrayet ville føre til "upsert" af et nyt dokument, når de ikke var opfyldt. Det ønskes ikke, derfor ingen "upsert".
Det, de faktisk gør, er henholdsvis at kontrollere, om array-elementet er til stede eller ej, og enten opdatere det eksisterende element eller oprette et nyt. Derfor i alt betyder alle operationer, at du enten ændrer "en gang" eller højst "to gange" i det tilfælde, hvor der opstod en upsert. Det mulige "to gange" skaber meget lidt overhead og intet reelt problem.
Også i den tredje sætning $not
operatør vender logikken i $elemMatch
for at bestemme, at der ikke findes noget array-element med forespørgselsbetingelsen.
Oversætter dette med .findOneAndUpdate()
bliver lidt mere et problem. Ikke kun er det "succesen", der betyder noget nu, det afgør også, hvordan det endelige indhold returneres.
Så den bedste idé her er at køre begivenhederne i "serier" og derefter arbejde lidt magi med resultatet for at returnere den ende "opdaterede" formular.
Den hjælp, vi vil bruge her, er både med async.waterfall og lodash bibliotek:
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);
}
);
Det følger samme logik som før, idet kun to eller en af disse udsagn faktisk vil gøre noget med muligheden for, at det "nye" dokument, der returneres, vil være null
. "Vandfaldet" sender her et resultat fra hver fase til den næste, inklusive den ende, hvor også enhver fejl straks vil forgrene sig til.
I dette tilfælde null
ville blive byttet til et tomt objekt {}
og _.merge()
metoden kombinerer de to objekter til ét, på hvert senere trin. Dette giver dig det endelige resultat, som er det modificerede objekt, uanset hvilke foregående operationer, der rent faktisk gjorde noget.
Selvfølgelig ville der være en anden manipulation påkrævet for $pull
, og også dit spørgsmål har inputdata som en objektform i sig selv. Men det er faktisk svar i sig selv.
Dette burde i det mindste få dig i gang med, hvordan du griber dit opdateringsmønster an.