MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Einfacherer Weg, ein Array mit MongoDB zu aktualisieren

Wenn es Ihnen wichtig ist, hier etwas mehr Funktionalität hinzuzufügen (sehr empfehlenswert) und den Aufwand für Aktualisierungen zu begrenzen, bei denen Sie das geänderte Dokument wirklich nicht zurückgeben müssen, oder selbst wenn Sie dies tun, ist es immer besser, atomare Operatoren zu verwenden mit Arrays wie $push und $addToSet .

Die "zusätzliche Funktionalität" besteht auch darin, dass es bei der Verwendung von Arrays im Speicher eine wirklich kluge Praxis ist, die "Länge" oder "Anzahl" von Elementen zu speichern. Dies wird in Abfragen nützlich und kann effizient mit einem "Index" aufgerufen werden, im Gegensatz zu anderen Methoden, um die "Anzahl" eines Arrays zu erhalten oder diese "Anzahl/Länge" für Filterzwecke zu verwenden.

Das bessere Konstrukt hier ist die Verwendung von "Bulk"-Operationen da das Testen auf vorhandene Array-Elemente nicht gut mit dem Konzept von "Upserts" mischt. Wenn Sie also Upsert-Funktionalität wünschen, ist ein Array-Test in zwei Operationen besser. Aber da "Bulk"-Operationen mit "einer Anfrage" an den Server gesendet werden können und Sie auch "eine Antwort" erhalten, verringert dies jeden wirklichen Overhead dabei.

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
});

Dies funktioniert so, dass der erste Versuch dort versucht, ein Dokument zu finden, in dem das hinzuzufügende Array-Element nicht bereits innerhalb des Arrays vorhanden ist. Hier wird kein Versuch eines "Upsert" unternommen, da Sie kein neues Dokument erstellen möchten, wenn der einzige Grund, warum es nicht mit einem Dokument übereinstimmt, darin besteht, dass das Array-Element nicht vorhanden ist. Aber wo es passt, wird das neue Mitglied dem Array hinzugefügt und die aktuelle "Zählung" wird um 1 "erhöht" über $inc , das die Gesamtanzahl oder -länge beibehält.

Die zweite Anweisung wird daher nur mit dem Dokument übereinstimmen und verwendet daher ein "Upsert", da das Dokument erstellt wird, wenn es nicht für das Schlüsselfeld gefunden wird. Da sich alle Operationen innerhalb von $setOnInsert dann wird keine Operation ausgeführt, wenn das Dokument bereits existiert.

Es ist wirklich alles nur eine Serveranfrage und -antwort, also gibt es kein "Hin und Her" für die Einbeziehung von zwei Aktualisierungsvorgängen, und das macht dies effizient.

Das Entfernen eines Array-Eintrags ist im Grunde das Gegenteil, außer dass diesmal kein neues Dokument "erstellt" werden muss, wenn es nicht gefunden wurde:

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
});

Jetzt müssen Sie also nur noch testen, wo das Array-Element vorhanden ist und wo es sich dann befindet $pull das übereinstimmende Element aus dem Array-Inhalt, während gleichzeitig der "Zähler" um 1 "erniedrigt" wird, um das Entfernen widerzuspiegeln.

Jetzt "könnten" Sie $addToSet verwenden Stattdessen hier, da es nur den Array-Inhalt betrachtet und wenn das Mitglied nicht gefunden wird, wird es hinzugefügt, und aus den gleichen Gründen besteht keine Notwendigkeit, das vorhandene Array-Element zu testen, wenn $pull da es einfach nichts tut, wenn das Element nicht vorhanden ist. Außerdem $addToSet kann in diesem Zusammenhang direkt innerhalb eines "Upsert" verwendet werden, solange Sie sich nicht "überkreuzen", da es nicht erlaubt ist, mehrere Update-Operatoren auf demselben Pfad mit MongoDB zu verwenden:

FollowModel.update(
    { "facebookId": req.user.facebookId },
    {
        "$setOnInsert": {
            "fans": []
        },
        "$addToSet": { "players": req.body.idToFollow }
    },
    { "upsert": true },
    function(err,numAffected) {
        // handling in here
    }
);

Aber das wäre "falsch":

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
    }
);

Dadurch verlieren Sie jedoch die "Zähl"-Funktionalität, da solche Operationen einfach abgeschlossen werden, ohne Rücksicht darauf, was tatsächlich vorhanden ist oder ob etwas "hinzugefügt" oder "entfernt" wurde.

Das Aufbewahren von "Zählern" ist eine wirklich gute Sache, und selbst wenn Sie im Moment keine unmittelbare Verwendung für sie haben, werden Sie sie wahrscheinlich irgendwann im Lebenszyklus Ihrer Anwendung benötigen. Es macht also sehr viel Sinn, die damit verbundene Logik zu verstehen und sie jetzt umzusetzen. Jetzt kleiner Preis für später viel Nutzen.

Kurze Randbemerkung hier, da ich im Allgemeinen "Massen"-Operationen empfehle, wo immer dies möglich ist. Bei Verwendung über die .collection accessor in mongoose, dann müssen Sie sich darüber im Klaren sein, dass dies native Treibermethoden sind und sich daher anders verhalten als die "mongoose"-Methoden.

Bemerkenswerterweise haben alle "Mungo"-Methoden eine eingebaute "Prüfung", um zu sehen, ob die Verbindung zur Datenbank gerade aktiv ist. Wo dies nicht der Fall ist, wird die Operation effektiv "in die Warteschlange" gestellt, bis die Verbindung hergestellt ist. Mit den nativen Methoden ist diese "Prüfung" nicht mehr vorhanden. Daher müssen Sie entweder sicher sein, dass bereits eine Verbindung von einer "Mungo"-Methode vorhanden ist, die "zuerst" ausgeführt wurde, oder alternativ Ihre gesamte Anwendungslogik in ein Konstrukt einschließen, das auf die Herstellung der Verbindung "wartet":

mongoose.connection.on("open",function(err) {
    // All app logic or start in here
});

So sind Sie sicher, dass eine Verbindung besteht und die richtigen Objekte zurückgegeben und von den Methoden verwendet werden können. Aber keine Verbindung, und die "Bulk"-Vorgänge werden fehlschlagen.