Als het u wel interesseert om hier wat meer functionaliteit toe te voegen (zeer aan te raden) en het beperken van de overhead van updates waarbij u het gewijzigde document echt niet hoeft terug te sturen, of zelfs als u dat wel doet, is het altijd beter om atomaire operatoren te gebruiken met arrays zoals $push
en $addToSet
.
De "extra functionaliteit" is ook dat bij het gebruik van arrays in opslag, het echt een verstandige gewoonte is om de "lengte" of "telling" van items op te slaan. Dit wordt handig bij zoekopdrachten en is efficiënt toegankelijk met een "index", in tegenstelling tot andere methoden om de "telling" van een array te krijgen of die "telling/lengte" te gebruiken voor filterdoeleinden.
De betere constructie hier is om "Bulk"-bewerkingen te gebruiken omdat testen op aanwezige array-elementen niet goed samengaat met het concept van "upserts", dus waar je upsert-functionaliteit en array-testen wilt, is het beter in twee bewerkingen. Maar aangezien "Bulk"-bewerkingen met "één verzoek" naar de server kunnen worden verzonden en u ook "één antwoord" krijgt, vermindert dit de echte overhead daarbij.
var bulk = FollowModel.collection.initializeOrderedBulkOp();
// Try to add where not found in array
bulk.find({
"facebookId": req.user.facebookId,
"players": { "$ne": req.body.idToFollow }
}).updateOne({
"$push": { "players": req.body.idToFollow },
"$inc": { "playerCount": 1 }
});
// Otherwise create the document if not matched
bulk.find({
"facebookId": req.user.facebookId,
}).upsert().updateOne({
"$setOnInsert": {
"players": [req.body.idToFollow]
"playerCount": 1,
"fans": [],
"fanCount": 0
}
})
bulk.execute(function(err,result) {
// Handling in here
});
De manier waarop dit werkt, is dat de eerste poging daar een document probeert te vinden waar het toe te voegen array-element niet al in de array aanwezig is. Er wordt hier geen poging gedaan tot een "upsert", omdat u geen nieuw document wilt maken als de enige reden dat het niet met een document overeenkomt, is omdat het array-element niet aanwezig is. Maar als het overeenkomt, wordt het nieuwe lid toegevoegd aan de array en wordt de huidige "telling" met 1 "verhoogd" via $inc
, die het totale aantal of de lengte bijhoudt.
De tweede instructie komt daarom alleen overeen met het document en gebruikt daarom een "upsert", want als het document niet wordt gevonden voor het sleutelveld, wordt het gemaakt. Omdat alle bewerkingen binnen $setOnInsert
dan wordt er geen bewerking uitgevoerd als het document al bestaat.
Het is allemaal maar één serververzoek en -antwoord, dus er is geen "heen en weer" voor het opnemen van twee updatebewerkingen, en dat maakt dit efficiënt.
Het verwijderen van een array-item is in feite het omgekeerde, behalve dat het deze keer niet nodig is om een nieuw document te "maken" als het niet is gevonden:
var bulk = FollowModel.collection.initializeOrderedBulkOp();
// Try to remove where found in array
bulk.find({
"facebookId": req.user.facebookId,
"players": req.body.idToFollow
}).updateOne({
"$pull": { "players": req.body.idToFollow },
"$inc": { "playerCount": -1 }
});
bulk.execute(function(err,result) {
// Handling in here
});
Dus nu hoef je alleen maar te testen waar het array-element aanwezig is en waar het dan is $pull
het overeenkomende element uit de array-inhoud, en tegelijkertijd de "telling" met 1 "verlagend" om de verwijdering weer te geven.
Nu "zou" u $addToSet
. kunnen gebruiken in plaats daarvan hier omdat het alleen naar de array-inhoud kijkt en als het lid niet wordt gevonden, wordt het toegevoegd, en om vrijwel dezelfde redenen is het niet nodig om te testen op het bestaande array-element bij gebruik van $pull
omdat het gewoon niets zal doen als het element er niet is. Verder $addToSet
in die context kan het direct binnen een "upsert" worden gebruikt, zolang je niet "paden kruist", aangezien het niet is toegestaan om te proberen meerdere update-operators op hetzelfde pad te gebruiken met MongoDB:
FollowModel.update(
{ "facebookId": req.user.facebookId },
{
"$setOnInsert": {
"fans": []
},
"$addToSet": { "players": req.body.idToFollow }
},
{ "upsert": true },
function(err,numAffected) {
// handling in here
}
);
Maar dit zou "fout" zijn:
FollowModel.update(
{ "facebookId": req.user.facebookId },
{
"$setOnInsert": {
"players": [], // <-- This is a conflict
"fans": []
},
"$addToSet": { "players": req.body.idToFollow }
},
{ "upsert": true },
function(err,numAffected) {
// handling in here
}
);
Door dat te doen verliest u echter de "tel"-functionaliteit, aangezien dergelijke bewerkingen gewoon worden voltooid, ongeacht wat er daadwerkelijk is of dat er iets is "toegevoegd" of "verwijderd".
Het bijhouden van "tellers" is echt een goede zaak, en zelfs als je ze nu niet direct kunt gebruiken, zul je ze op een bepaald moment in de levenscyclus van je applicatie waarschijnlijk wel willen hebben. Het is dus heel logisch om de betrokken logica te begrijpen en deze nu te implementeren. Kleine prijs om nu te betalen voor veel voordeel later.
Korte kanttekening hier, aangezien ik over het algemeen "Bulk" -bewerkingen aanbeveel waar mogelijk. Bij gebruik hiervan via de .collection
accessor in mangoest, moet u zich ervan bewust zijn dat dit native driver-methoden zijn en zich daarom anders gedragen dan de "mongoose"-methoden.
Met name alle "mongoose"-methoden hebben een ingebouwde "controle" om te zien of de verbinding met de database momenteel actief is. Waar dit niet het geval is, wordt de bewerking in feite "in de wachtrij geplaatst" totdat de verbinding tot stand is gebracht. Bij gebruik van de native methodes is deze "check" niet meer aanwezig. Daarom moet je ofwel zeker weten dat er al een verbinding aanwezig is van een "mongoose"-methode die "eerste" heeft uitgevoerd, of je hele applicatielogica omwikkelen in een constructie die "wacht" tot de verbinding tot stand is gebracht:
mongoose.connection.on("open",function(err) {
// All app logic or start in here
});
Op die manier weet je zeker dat er een verbinding aanwezig is en de juiste objecten kunnen worden geretourneerd en gebruikt door de methoden. Maar geen verbinding en de "Bulk"-bewerkingen zullen mislukken.