sql >> Base de Datos >  >> NoSQL >> MongoDB

Manera más fácil de actualizar una matriz con MongoDB

Si "le importa" agregar un poco más de funcionalidad aquí (muy recomendable) y limitar la sobrecarga de actualizaciones donde realmente no necesita devolver el documento modificado, o incluso si lo hace, siempre es mejor usar operadores atómicos. con matrices como $push y $addToSet .

La "funcionalidad adicional" también se encuentra en que cuando se usan arreglos en el almacenamiento, es una práctica muy sabia almacenar la "longitud" o el "recuento" de elementos. Esto se vuelve útil en las consultas y se puede acceder de manera eficiente con un "índice", a diferencia de otros métodos para obtener el "recuento" de una matriz o usar ese "recuento/longitud" con fines de filtrado.

La mejor construcción aquí es usar operaciones "en bloque" como la prueba de los elementos de matriz presentes no se combina bien con el concepto de "upserts", por lo que si desea modificar la funcionalidad de una matriz, la prueba es mejor en dos operaciones. Pero dado que las operaciones "en bloque" se pueden enviar al servidor con "una solicitud" y también obtiene "una respuesta", esto mitiga cualquier sobrecarga real al hacerlo.

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

La forma en que esto funciona es que el primer intento intenta encontrar un documento donde el elemento de matriz que se agregará no está presente dentro de la matriz. No se intenta "upsert" aquí, ya que no desea crear un nuevo documento si la única razón por la que no coincide con un documento es porque el elemento de la matriz no está presente. Pero cuando coinciden, el nuevo miembro se agrega a la matriz y el "recuento" actual se "incrementa" en 1 a través de $inc , que mantiene el recuento total o la longitud.

Por lo tanto, la segunda declaración solo coincidirá con el documento y, por lo tanto, utiliza un "upsert", ya que si no se encuentra el documento para el campo clave, se creará. Como todas las operaciones están dentro de $setOnInsert entonces no se realizará ninguna operación si el documento ya existe.

En realidad, todo es solo una solicitud y respuesta del servidor, por lo que no hay "ida y vuelta" para la inclusión de dos operaciones de actualización, y eso lo hace eficiente.

Eliminar una entrada de matriz es básicamente lo contrario, excepto que esta vez no es necesario "crear" un nuevo documento si no se encuentra:

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

Entonces ahora solo necesita probar dónde está presente el elemento de matriz y dónde está $pull el elemento coincidente del contenido de la matriz, al mismo tiempo que "disminuye" el "recuento" en 1 para reflejar la eliminación.

Ahora "podría" usar $addToSet en cambio, aquí, ya que solo verá el contenido de la matriz y, si no se encuentra el miembro, se agregará, y por las mismas razones no es necesario probar el elemento de la matriz existente cuando se usa $pull ya que simplemente no hará nada si el elemento no está allí. Además $addToSet en ese contexto, se puede usar directamente dentro de un "upsert", siempre que no se "crucen caminos", ya que no está permitido intentar usar múltiples operadores de actualización en el mismo camino con MongoDB:

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

Pero esto sería "incorrecto":

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

Sin embargo, al hacerlo, perderá la función de "conteo", ya que tales operaciones simplemente se completan sin tener en cuenta lo que realmente hay allí o si se "agregó" o "eliminó algo".

Mantener "contadores" es algo realmente bueno, e incluso si no tiene un uso inmediato para ellos en este momento, en algún momento del ciclo de vida de su aplicación probablemente los querrá. Por lo tanto, tiene mucho sentido comprender la lógica involucrada e implementarla ahora. Pequeño precio a pagar ahora por muchos beneficios más adelante.

Nota al margen rápida aquí, ya que generalmente recomiendo operaciones "a granel" cuando sea posible. Al usar esto a través de .collection accessor en mongoose, entonces debe tener en cuenta que estos son métodos de controlador nativos y, por lo tanto, se comportan de manera diferente a los métodos "mongoose".

En particular, todos los métodos de "mangosta" tienen una "comprobación" incorporada para ver que la conexión a la base de datos está actualmente activa. Cuando no lo está, la operación se "pone en cola" hasta que se establece la conexión. Usando los métodos nativos, este "cheque" ya no está presente. Por lo tanto, debe asegurarse de que una conexión ya esté presente desde un método "mangosta" que se ejecutó "primero", o alternativamente envolver toda la lógica de la aplicación en una construcción que "espera" a que se realice la conexión:

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

De esa manera, está seguro de que hay una conexión presente y los métodos pueden devolver y utilizar los objetos correctos. Pero sin conexión, y las operaciones "en bloque" fallarán.