Обновление вложенных массивов в mongodb

У меня есть документ в mongodb с 2-уровневым вложенным массивом объектов, который мне нужно обновить, примерно так:

{
    id: 1,
    items: [
        {
            id: 2,
            blocks: [
                {
                    id: 3
                    txt: 'hello'
                }
            ]
        }
    ] 
}

Если бы существовал только один уровень глубокого массива, я мог бы использовать позиционный оператор для обновления объектов в нем, но для второго уровня единственным вариантом, который я выбрал, является использование позиционного оператора с индексом вложенных объектов, например:

db.objects.update({'items.id': 2}, {'$set': {'items.$.blocks.0.txt': 'hi'}})

Этот подход работает, но мне кажется опасным, поскольку я строю веб-службу, а индексный номер должен поступать от клиента, который может отправить 100000 в качестве индекса, и это заставит mongodb создать массив с 100000 индексами с нулевым значением.

Существуют ли какие-либо другие способы обновления таких вложенных объектов, где я могу ссылаться на идентификатор объекта вместо его положения или, возможно, на способы проверки того, был ли предоставленный индекс за пределами, прежде чем использовать его в запросе?

Ответ 1

Вот большой вопрос: нужно ли вам использовать Mongo "addToSet" и "push" операции? Если вы действительно планируете изменять только отдельные элементы в массиве, то вы должны, вероятно, построить эти массивы как объекты.

Вот как бы я это структурировал:

{
    id: 1,
    items: 
        { 
          "2" : { "blocks" : { "3" : { txt : 'hello' } } },
          "5" : { "blocks" : { "1" : { txt : 'foo'}, "2" : { txt : 'bar'} } }
        }
}

В основном это преобразует все в объекты JSON вместо массивов. Вы теряете возможность использовать $push и $addToSet, но я думаю, что это делает все проще. Например, ваш запрос будет выглядеть так:

db.objects.update({'items.2': {$exists:true} }, {'$set': {'items.2.blocks.0.txt': 'hi'}})

Вы также заметите, что я сбросил идентификаторы. Когда вы вложите такие вещи, вы обычно можете заменить "ID" простым использованием этого числа в качестве индекса. Теперь подразумевается концепция "ID".

Эта функция была добавлена ​​в 3.6 с выразительными обновлениями.

db.objects.update( {id: 1 }, { $set: { 'items.$[itm].blocks.$[blk].txt': "hi", } }, { multi: false, arrayFilters: [ { 'itm.id': 2 }, { 'blk.id': 3} ] } )

Ответ 2

Иды, которые вы используете, являются линейным числом, и оно должно происходить откуда-то вроде дополнительного поля, такого как max_idx или что-то подобное. Это означает один поиск идентификатора, а затем обновление. UUID/ObjectId можно использовать для идентификаторов, которые гарантируют, что вы также можете использовать Distributed CRUD.