MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Modo più semplice per aggiornare un array con MongoDB

Se "ti interessa" aggiungere un po' più di funzionalità qui (molto consigliato) e limitare il sovraccarico degli aggiornamenti in cui non hai davvero bisogno di restituire il documento modificato, o anche se lo fai, è sempre meglio usare gli operatori atomici con array come $push e $addToSet .

La "funzionalità aggiuntiva" sta anche nel fatto che quando si utilizzano gli array nella memoria, è una pratica davvero saggia memorizzare la "lunghezza" o il "conteggio" degli elementi. Questo diventa utile nelle query ed è possibile accedervi in ​​modo efficiente con un "indice", al contrario di altri metodi per ottenere il "conteggio" di un array o utilizzare quel "conteggio/lunghezza" per scopi di filtro.

Il miglior costrutto qui è usare Operazioni "bulk" poiché il test per gli elementi dell'array presenti non si combina bene con il concetto di "upsert", quindi dove si desidera una funzionalità upsert un test dell'array è meglio in due operazioni. Ma poiché le operazioni "bulk" possono essere inviate al server con "una richiesta" e si ottiene anche "una risposta", questo riduce qualsiasi sovraccarico reale nel farlo.

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

Il modo in cui funziona è che il primo tentativo tenta di trovare un documento in cui l'elemento dell'array da aggiungere non è già presente all'interno dell'array. Non viene effettuato alcun tentativo di "upsert" qui poiché non si desidera creare un nuovo documento se l'unico motivo per cui non corrisponde a un documento è perché l'elemento dell'array non è presente. Ma dove corrisponde, il nuovo membro viene aggiunto all'array e il "conteggio" corrente viene "incrementato" di 1 tramite $inc , che mantiene il conteggio o la lunghezza totale.

La seconda istruzione corrisponderà quindi solo al documento e quindi utilizza un "upsert" poiché se il documento non viene trovato per il campo chiave, verrà creato. Poiché tutte le operazioni sono all'interno di $setOnInsert quindi non verrà eseguita alcuna operazione se il documento esiste già.

In realtà è tutto solo una richiesta e una risposta del server, quindi non c'è "avanti e indietro" per l'inclusione di due operazioni di aggiornamento, e questo lo rende efficiente.

La rimozione di una voce di matrice è sostanzialmente l'inverso, tranne per il fatto che questa volta non è necessario "creare" un nuovo documento se non è stato trovato:

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

Quindi ora devi solo verificare dove è presente l'elemento dell'array e dove si trova quindi $pull l'elemento corrispondente dal contenuto dell'array, allo stesso tempo "diminuendo" il "conteggio" di 1 per riflettere la rimozione.

Ora "potresti" usare $addToSet invece qui come guarderà solo il contenuto dell'array e se il membro non viene trovato, verrà aggiunto, e per lo stesso motivo non è necessario testare l'elemento dell'array esistente quando si usa $pull poiché non farà nulla se l'elemento non è presente. Inoltre $addToSet in quel contesto può essere utilizzato direttamente all'interno di un "upsert", purché non si "incroci percorsi" poiché non è consentito provare a utilizzare più operatori di aggiornamento sullo stesso percorso con MongoDB:

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

Ma questo sarebbe "sbagliato":

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

Tuttavia, così facendo perdi la funzionalità di "conteggio" poiché tali operazioni vengono semplicemente completate indipendentemente da ciò che è effettivamente presente o se qualcosa è stato "aggiunto" o "rimosso".

Mantenere i "contatori" è davvero una buona cosa, e anche se non ne hai un uso immediato in questo momento, a un certo punto del ciclo di vita della tua applicazione probabilmente li vorrai. Quindi ha molto senso capire la logica coinvolta e implementarla ora. Piccolo prezzo da pagare ora per molti vantaggi in seguito.

Nota a margine rapida qui poiché generalmente raccomando operazioni "in blocco" ove possibile. Quando si utilizza questo tramite il .collection accessor in mongoose, allora devi essere consapevole che questi sono metodi driver nativi e quindi si comportano in modo diverso dai metodi "mongoose".

In particolare, tutti i metodi "mangusta" hanno un "controllo" integrato per verificare che la connessione al database sia attualmente attiva. In caso contrario, l'operazione è effettivamente "in coda" fino a quando non viene effettuata la connessione. Utilizzando i metodi nativi questo "controllo" non è più presente. Pertanto devi essere sicuro che sia già presente una connessione da un metodo "mangusta" che ha eseguito "prima", o in alternativa racchiudere l'intera logica dell'applicazione in un costrutto che "aspetta" che venga stabilita la connessione:

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

In questo modo sei sicuro che sia presente una connessione e che gli oggetti corretti possano essere restituiti e utilizzati dai metodi. Ma nessuna connessione e le operazioni "bulk" falliranno.