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

Inserisci il documento e/o aggiungi un documento secondario

L'approccio alla gestione di questo non è semplice, poiché mescolare "upsert" con l'aggiunta di elementi agli "array" può facilmente portare a risultati indesiderati. Dipende anche se vuoi che la logica imposti altri campi come un "contatore" che indica quanti contatti ci sono all'interno di un array, che vuoi solo aumentare/diminuire man mano che gli elementi vengono rispettivamente aggiunti o rimossi.

Nel caso più semplice invece, se i "contatti" contenevano solo un valore singolare come un ObjectId collegamento a un'altra raccolta, quindi $addToSet il modificatore funziona bene, purché non siano coinvolti "contatori":

Client.findOneAndUpdate(
    { "clientName": clientName },
    { "$addToSet": { "contacts":  contact } },
    { "upsert": true, "new": true },
    function(err,client) {
        // handle here
    }
);

E va tutto bene dato che stai solo testando per vedere se un documento corrisponde a "clientName", se non lo inserisci. Indipendentemente dal fatto che ci sia una corrispondenza o meno, il $addToSet l'operatore si prenderà cura dei valori "singolari" univoci, essendo qualsiasi "oggetto" veramente unico.

Le difficoltà arrivano quando hai qualcosa come:

{ "firstName": "John", "lastName": "Smith", "age": 37 }

Già nell'array dei contatti, quindi vuoi fare qualcosa del genere:

{ "firstName": "John", "lastName": "Smith", "age": 38 }

Dove la tua vera intenzione è che questo sia lo "stesso" John Smith, ed è solo che l'"età" non è diversa. Idealmente, vuoi semplicemente "aggiornare" quella voce dell'array e non creare un nuovo array o un nuovo documento.

Funzionando con .findOneAndUpdate() dove si desidera restituire il documento aggiornato può essere difficile. Quindi, se non vuoi davvero il documento modificato in risposta, allora API per operazioni in blocco di MongoDB e il driver principale sono di grande aiuto qui.

Considerando le affermazioni:

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

Questo è bello poiché le operazioni in blocco qui significano che tutte le istruzioni qui vengono inviate al server contemporaneamente e c'è solo una risposta. Nota anche qui che la logica qui significa che al massimo solo due operazioni modificheranno effettivamente qualsiasi cosa.

In primo luogo, il $setOnInsert modificatore assicura che nulla venga modificato quando il documento è solo una corrispondenza. Poiché le uniche modifiche qui sono all'interno di quel blocco, ciò riguarda solo un documento in cui si verifica un "upsert".

Nota anche sulle prossime due affermazioni che non provi a "ribaltare" di nuovo. Ciò ritiene che la prima affermazione abbia avuto successo dove doveva essere, o comunque non avesse importanza.

L'altro motivo per l'assenza di "upsert" è perché le condizioni necessarie per testare la presenza dell'elemento nell'array porterebbero all'"upsert" di un nuovo documento quando non sono state soddisfatte. Ciò non è desiderato, quindi nessun "ribaltamento".

Quello che fanno infatti è rispettivamente controllare se l'elemento dell'array è presente o meno e aggiornare l'elemento esistente o crearne uno nuovo. Pertanto, in totale, tutte le operazioni significano che si modifica "una volta" o al massimo "due volte" nel caso in cui si sia verificato un upsert. Il possibile "due volte" crea un sovraccarico minimo e nessun problema reale.

Anche nella terza affermazione il $not l'operatore inverte la logica di $elemMatch per determinare che non esiste alcun elemento dell'array con la condizione di query.

Traducendo questo con .findOneAndUpdate() diventa un po' più un problema. Non è solo il "successo" che conta ora, ma determina anche come viene restituito l'eventuale contenuto.

Quindi l'idea migliore qui è quella di eseguire gli eventi in "serie" e poi fare un po' di magia con il risultato per restituire il modulo "aggiornato" alla fine.

L'aiuto che useremo qui è sia con async.waterfall e il lodash biblioteca:

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

Ciò segue la stessa logica di prima in quanto solo due o una di queste istruzioni in realtà faranno qualcosa con la possibilità che il "nuovo" documento restituito sarà null . La "cascata" qui passa un risultato da ogni fase alla successiva, inclusa la fine in cui anche qualsiasi errore si ramificherà immediatamente.

In questo caso il null verrebbe scambiato con un oggetto vuoto {} e il _.merge() metodo unirà i due oggetti in uno, in ogni fase successiva. Questo ti dà il risultato finale che è l'oggetto modificato, indipendentemente dalle operazioni precedenti che hanno effettivamente fatto qualcosa.

Ovviamente, ci sarebbe una manipolazione diversa richiesta per $pull , e anche la tua domanda ha dati di input come modulo oggetto in sé. Ma queste sono in realtà risposte in se stesse.

Questo dovrebbe almeno farti iniziare su come affrontare il tuo modello di aggiornamento.