Podejście do radzenia sobie z tym nie jest proste, ponieważ mieszanie „upsertów” z dodawaniem elementów do „tablic” może łatwo prowadzić do niepożądanych rezultatów. Zależy to również od tego, czy logika ma ustawić inne pola, takie jak „licznik” wskazujący, ile kontaktów znajduje się w tablicy, które chcesz tylko zwiększać/zmniejszać, gdy elementy są odpowiednio dodawane lub usuwane.
Jednak w najprostszym przypadku, jeśli "kontakty" zawierały tylko pojedynczą wartość, taką jak ObjectId
linkowanie do innej kolekcji, a następnie $addToSet
modyfikator działa dobrze, o ile nie ma żadnych „liczników”:
Client.findOneAndUpdate(
{ "clientName": clientName },
{ "$addToSet": { "contacts": contact } },
{ "upsert": true, "new": true },
function(err,client) {
// handle here
}
);
I wszystko jest w porządku, ponieważ testujesz tylko, aby sprawdzić, czy dokument pasuje do "nazwa klienta", jeśli nie, podmień go. Niezależnie od tego, czy pasuje, czy nie, $addToSet
operator zadba o unikalne „pojedyncze” wartości, będące dowolnym „obiektem”, który jest naprawdę wyjątkowy.
Trudności pojawiają się, gdy masz coś takiego:
{ "firstName": "John", "lastName": "Smith", "age": 37 }
Już w tablicy kontaktów, a następnie chcesz zrobić coś takiego:
{ "firstName": "John", "lastName": "Smith", "age": 38 }
W przypadku, gdy twoją rzeczywistą intencją jest to, że jest to „ten sam” John Smith, a po prostu „wiek” nie jest inny. Najlepiej byłoby po prostu „zaktualizować” ten wpis w tablicy, nie tworząc nowej tablicy lub nowego dokumentu.
Praca z .findOneAndUpdate()
miejsce, w którym chcesz zwrócić zaktualizowany dokument, może być trudne. Więc jeśli naprawdę nie chcesz, aby zmodyfikowany dokument w odpowiedzi, to Interfejs API operacji zbiorczych
MongoDB i główny sterownik są tutaj najbardziej pomocne.
Biorąc pod uwagę wypowiedzi:
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
});
Jest to miłe, ponieważ operacje zbiorcze oznaczają, że wszystkie oświadczenia są wysyłane do serwera jednocześnie i jest tylko jedna odpowiedź. Zauważ również, że logika oznacza tutaj, że co najwyżej dwie operacje faktycznie zmodyfikują cokolwiek.
W pierwszym przypadku $setOnInsert
modyfikator zapewnia, że nic nie zostanie zmienione, gdy dokument jest tylko dopasowaniem. Ponieważ jedyne modyfikacje znajdują się w tym bloku, dotyczy to tylko dokumentu, w którym występuje „upsert”.
Zwróć też uwagę na kolejne dwa stwierdzenia, których nie próbujesz ponownie „przewracać”. Uważa to, że pierwsze stwierdzenie prawdopodobnie odniosło sukces tam, gdzie musiało być, lub w inny sposób nie miało znaczenia.
Innym powodem braku „upsert” jest to, że warunki potrzebne do przetestowania obecności elementu w tablicy doprowadziłyby do „upsert” nowego dokumentu, gdy nie zostałyby spełnione. To nie jest pożądane, dlatego nie ma „przewracania się”.
W rzeczywistości sprawdzają odpowiednio, czy element tablicy jest obecny, czy nie, i aktualizują istniejący element lub tworzą nowy. Dlatego w sumie wszystkie operacje oznaczają, że modyfikujesz „raz” lub co najwyżej „dwukrotnie” w przypadku wystąpienia upsert. Ewentualne „dwukrotne” powoduje bardzo niewielkie obciążenie i nie ma prawdziwego problemu.
Również w trzecim stwierdzeniu $not
operator odwraca logikę $elemMatch
aby ustalić, że nie istnieje żaden element tablicy z warunkiem zapytania.
Tłumaczenie tego za pomocą .findOneAndUpdate()
staje się nieco większym problemem. Teraz liczy się nie tylko „sukces”, ale także sposób, w jaki zostanie zwrócona ostateczna treść.
Najlepszym pomysłem jest więc uruchomienie wydarzeń w „seriach”, a następnie trochę magii z wynikiem, aby zwrócić końcowy „zaktualizowany” formularz.
Pomoc, której tutaj użyjemy, dotyczy zarówno async.waterfall i lodash biblioteka:
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);
}
);
Jest to zgodne z tą samą logiką, co poprzednio, ponieważ tylko dwie lub jedna z tych instrukcji faktycznie zrobi cokolwiek z możliwością, że zwrócony "nowy" dokument będzie miał wartość null
. Tutaj „wodospad” przekazuje wynik z każdego etapu do następnego, w tym do końca, do którego również każdy błąd natychmiast się rozgałęzia.
W tym przypadku null
zostanie zamieniony na pusty obiekt {}
i _.merge()
Metoda połączy oba obiekty w jeden, na każdym późniejszym etapie. Daje to końcowy wynik, którym jest zmodyfikowany obiekt, bez względu na to, które poprzednie operacje faktycznie cokolwiek zrobiły.
Oczywiście wymagana byłaby inna manipulacja dla $pull
, a także Twoje pytanie zawiera dane wejściowe jako formę obiektu. Ale to są właściwie odpowiedzi same w sobie.
Powinno to przynajmniej pomóc w podejściu do wzorca aktualizacji.