MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Maneira mais fácil de atualizar um array com o MongoDB


Se você "se importa" em adicionar um pouco mais de funcionalidade aqui (muito aconselhável) e limitar a sobrecarga de atualizações onde você realmente não precisa retornar o documento modificado, ou mesmo se você fizer isso, é sempre melhor usar operadores atômicos com matrizes como $push e $addToSet .

A "funcionalidade adicional" também está em que, ao usar arrays no armazenamento, é uma prática muito sábia armazenar o "comprimento" ou "contagem" dos itens. Isso se torna útil em consultas e pode ser acessado de forma eficiente com um "índice", em oposição a outros métodos de obter a "contagem" de uma matriz ou usar essa "contagem/comprimento" para fins de filtragem.

A melhor construção aqui é usar "Bulk" operações como o teste de elementos de matriz presentes não combina bem com o conceito de "upserts", portanto, onde você deseja a funcionalidade de upsert em um teste de matriz, é melhor em duas operações. Mas como as operações "em massa" podem ser enviadas ao servidor com "uma solicitação" e você também obtém "uma resposta", isso reduz qualquer sobrecarga real ao fazê-lo.
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
});

A maneira como isso funciona é que a primeira tentativa tenta encontrar um documento onde o elemento da matriz a ser adicionado não está presente já dentro da matriz. Nenhuma tentativa é feita em um "upsert" aqui, pois você não deseja criar um novo documento se a única razão pela qual ele não corresponde a um documento é porque o elemento da matriz não está presente. Mas onde houver correspondência, o novo membro será adicionado à matriz e a "contagem" atual será "incrementada" em 1 via $inc , que mantém a contagem ou comprimento total.

A segunda instrução, portanto, corresponderá apenas ao documento e, portanto, usa um "upsert", pois se o documento não for encontrado para o campo-chave, ele será criado. Como todas as operações estão dentro de $setOnInsert então não será realizada nenhuma operação se o documento já existir.

É tudo apenas uma solicitação e resposta do servidor, portanto, não há "vai e volta" para a inclusão de duas operações de atualização, e isso torna isso eficiente.

A remoção de uma entrada de array é basicamente o inverso, exceto que desta vez não há necessidade de "criar" um novo documento se ele não for encontrado:
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
});

Então agora você só precisa testar onde o elemento array está presente e onde está então $pull o elemento correspondente do conteúdo da matriz, ao mesmo tempo que "diminui" a "contagem" em 1 para refletir a remoção.

Agora você "poderia" usar $addToSet em vez disso aqui, pois ele apenas observará o conteúdo do array e, se o membro não for encontrado, ele será adicionado e, pelas mesmas razões, não há necessidade de testar o elemento do array existente ao usar $pull pois não fará nada se o elemento não estiver lá. Além disso $addToSet nesse contexto pode ser usado diretamente dentro de um "upsert", desde que você não "cruze caminhos", pois não é permitido tentar usar vários operadores de atualização no mesmo caminho com o MongoDB:
FollowModel.update(
    { "facebookId": req.user.facebookId },
    {
        "$setOnInsert": {
            "fans": []
        },
        "$addToSet": { "players": req.body.idToFollow }
    },
    { "upsert": true },
    function(err,numAffected) {
        // handling in here
    }
);

Mas isso seria "errado":
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
    }
);

No entanto, ao fazer isso, você perde a funcionalidade de "contagem", pois essas operações estão apenas sendo concluídas sem considerar o que realmente está lá ou se algo foi "adicionado" ou "removido".

Manter "contadores" é uma coisa muito boa, e mesmo que você não tenha um uso imediato para eles agora, então em algum estágio do ciclo de vida do seu aplicativo você provavelmente os desejará. Portanto, faz muito sentido entender a lógica envolvida e implementá-la agora. Pequeno preço a pagar agora para muitos benefícios mais tarde.

Nota lateral rápida aqui, pois geralmente recomendo operações "em massa" sempre que possível. Ao usar isso através do .collection accessor em mangusto, você precisa estar ciente de que esses são métodos de driver nativos e, portanto, se comportam de maneira diferente dos métodos "mangusto".

Notavelmente, todos os métodos "mangusto" têm uma "verificação" interna para ver se a conexão com o banco de dados está ativa no momento. Onde não estiver, a operação é efetivamente "enfileirada" até que a conexão seja feita. Usando os métodos nativos, essa "verificação" não está mais presente. Portanto, você precisa ter certeza de que uma conexão já está presente a partir de um método "mangusto" executado "primeiro" ou, alternativamente, envolver toda a lógica do aplicativo em uma construção que "espera" pela conexão ser feita:
mongoose.connection.on("open",function(err) {
    // All app logic or start in here
});

Dessa forma, você tem certeza de que há uma conexão presente e os objetos corretos podem ser retornados e usados ​​pelos métodos. Mas nenhuma conexão e as operações "em massa" falharão.