FindOneAndUpdate вызывает проблему дублирования

У меня проблема с findOneAndUpdate в мангусте. Дело в том, что я обновляю документ, найдя его. Запрос выглядит следующим образом:

UserModel.findOneAndUpdate({
individualId: 'some id'
}, {
$push: {
supporterOf: 'some string'
}
})

«supporterOf» — это ссылка на UserModel, а его тип — «ObjectId».

The issue i am facing here is that, 'some string' is being pushed twice under 'supporterOf' in the document.

Может ли кто-нибудь сказать мне, как вставить элемент массива внутрь документа?

Ваш запрос выглядит нормально, я считаю, что ваша проблема кроется где-то еще в вашем коде. Не могли бы вы опубликовать остальную часть вашей конечной точки? Вы пытались вручную получить документ, поместив строку в массив и сохранив ее с помощью .save()?

BenSower 11.03.2019 15:34

Нет, я не пробовал ручную выборку, а затем обновление .save(), потому что я хочу, чтобы транзакция выполнялась за один раз. Я хочу найти и обновить результат, его обновление, но вставку повторяющегося элемента. У меня также есть отладка кода, я получаю одно значение из конечной точки. Не могли бы вы сказать мне, будет ли время транзакции одинаковым, когда я (ручная выборка и отправка) и когда я (использую findOneAndUpdate)?

Syed Faizan Ahmed 12.03.2019 12:04

Это будет медленнее, но если вы не планируете обновлять много (например, 1000+) документов одновременно или у вас очень низкие аппаратные ограничения, вы сможете пренебречь этими различиями. Тем не менее, я бы посоветовал вам попытаться отладить эту проблему, поскольку я считаю, что может быть основная проблема, которая может вызвать дальнейшие проблемы в будущем. Поэтому, не могли бы вы опубликовать доказательство концепции вашей проблемы?

BenSower 13.03.2019 11:26

Да, @BenSower, вы правы, это будет медленнее, но поверьте мне, это происходит прямо сейчас, когда findOneAndUpdate вставляет повторяющуюся запись в Pushed items. Чтобы повторить проблему, создайте схему, в которой у вас есть пустой массив. Затем запустите findOneAndUpdate и нажмите элементы, как в предыдущем посте, вы получите ту же проблему с повторяющимися записями.

Syed Faizan Ahmed 20.03.2019 12:32

Я создал этот gist gist.github.com/BenSower/9800a21c2ae4202d81a46fc64bc55b9e, который дважды вызывает findOneAndUpdate и каждый раз отправляет только одну строку. Какую версию мангуста вы используете?

BenSower 21.03.2019 14:39
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
2
5
2 823
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Недавно я столкнулся с той же проблемой. Тем не менее, мне удалось решить эту проблему с помощью некоторых других логик (подробности приведены ниже), но я не мог понять причину того, почему найтиOneAndUpdate вставляет записи дубликат в mongodb.

Вы можете решить эту проблему, следуя логике.

Используйте найтиодин или найти по идентификатору вместо найтиOneAndUpdate для поиска документа в вашей коллекции, а затем вручную обновите документ и запустите спасти().

Вы можете лучше понять этот фрагмент кода

return new Promise(function (resolve, reject) {
    Model.findOne({
            someCondition...
        }, function (err, item) {
            if (err) {
                reject(err);
            } else {
                item.someArray.push({
                    someKeyValue...
                });
                item.save().then((result) => {
                    resolve(result)
                }).catch((err) => {
                    reject(err)
                });
            }
        }).catch((err) => {
            reject(err)
        });
   });

Это не будет вставлять повторяющийся элемент. Однако, если вы узнаете причину дублирования, обязательно обновите эту ветку.

@Faizy Вы знаете, что мангуст также напрямую возвращает обещание (если только вы не используете довольно старую версию), поэтому вам не нужно снова заключать его в другое обещание?

BenSower 13.03.2019 11:27

@BenSower По сути, Асад Улла рассказал мне обходной путь дублирования записей. Я хочу отменить повторяющиеся записи из базы данных, и прямо сейчас findOneAndUpdate вызывает проблему повторяющихся записей.

Syed Faizan Ahmed 20.03.2019 12:24

Да, но это не меняет того факта, что можно улучшить стиль кода, используя более понятные функции ;-)

BenSower 21.03.2019 12:48

У меня была такая же проблема, решение есть.

Я ждал, как показано ниже.

 **await** schema.findOneAndUpdate(queryParms, {
                "$push": {
                    "array1": arrayDetails,
                    "array2": array2Details
                }
            }, {
                "upsert": true,
                "new": true
            },
            function (error, updateResponse) {
                if (error) {
                    throw new Error (error);
                } else {
                    // do something with updateResponse;
                }
            });

простое удаление ожидания помогло мне решить эту проблему. Нужно найти первопричину. любой указатель для ссылок приветствуется.

«Смешивание обещаний и обратных вызовов может привести к дублированию записей в массивах», прямо из mongoosejs.com/docs/queries.html

Sga 31.03.2021 12:27

У меня была такая же проблема. Мой код был:

const doc = await model.findOneAndUpdate(
{filter}, {update},
{new: true}, (err, item) =>  if (err) console.info(err) }
)
res.locals.doc = doc
next();

Дело в том, что по какой-то причине этот обратный вызов после «новой» опции создавал двойную запись. Я удалил обратный вызов, и это сработало.

У меня такая же проблема. Я нашел решение для себя:

Я использовал обратный вызов и обещание (поэтому использовал ключевое слово «ожидание») одновременно.

Using a callback and a promise simultaneously will result in the query being executed twice. You should be using one or the other, but not both.

  options = {
    upsert: true  // creates the object if it doesn't exist. defaults to false.
  };
  await Company.findByIdAndUpdate(company._id,
    { $push: { employees: savedEmployees } },
    options,
    (err) => {
       if (err) {
          debug(err);
       }
    }
  ).exec();

к

  options = {
    upsert: true  // creates the object if it doesn't exist. defaults to false.
  };
  await Company.findByIdAndUpdate(company._id,
    { $push: { employees: savedEmployees } },
    options
  ).exec();
UserModel.findOneAndUpdate(
{ _id: id },
{ object }
)

Даже если вы используете _id в качестве параметра, не забудьте сделать фильтр явным по id

Проблема с принятым ответом заключается в том, что он решает проблему только путем заключения его в ненужное дополнительное обещание, когда метод findOneAndUpdate() уже возвращает обещание. Кроме того, он использует как обещания, так и обратные вызовы, чего вам почти никогда не следует делать.

Вместо этого я бы выбрал следующий подход:

Обычно я предпочитаю отделять логику запроса на обновление от других проблем как для удобочитаемости, так и для повторного использования. поэтому я бы сделал функцию-оболочку вроде:

const update = (id, updateObj) => {
    const options = {
      new: true,
      upsert: true
    }
    return model.findOneAndUpdate({_id: id}, {...updateObj}, options).exec()
}

Затем эту функцию можно было бы повторно использовать во всем моем приложении, избавляя меня от необходимости переписывать повторяющиеся настройки параметров или вызовы exec.

Затем у меня была бы какая-то другая функция, отвечающая за вызов моего запроса, передачу ему значений и обработку того, что возвращается от него.

Что-то вроде:

const makePush = async () => {
   try {
     const result = await update('someObjectId', {$push: {someField: value}});
     // do whatever you want to do with the updated document
   catch (e) {
     handleError(e)
    }
 }

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

Проблема, похоже, связана с объединением ожидания и обратного вызова. У меня была такая же проблема, пока я не понял, что использую обратный вызов (err, resp) а также a .catch(...).

models[auxType].findOneAndUpdate(
    filter,
    updateObject,
    options,
    (err, resp)=>{
        if (err) {
            console.info("Update failed:",err)
            res.json(err)
        } else if (resp) {
            console.info("Update succeeded:",resp)
            res.json(resp)
        } else {
            console.info("No error or response returned by server")
        }
    })
    .catch((e)=>{console.info("Error saving Aux Edit:",e)}); // << THE PROBLEM WAS HERE!!

Проблема решилась, как только я удалил строку .catch(...).

Из документации мангуста:

  • «Запросы Mongoose не являются промисами. У них есть функция .then() для co и async/await для удобства. Однако, в отличие от промисов, вызов .then() запроса может выполнить запрос несколько раз». (https://mongoosejs.com/docs/queries.html#queries-не-обещания)

В моем случае изменение обратного вызова асинхронный решило проблему.

изменить это:

await schema.findOneAndUpdate(
    { queryData },
    { updateData },
    { upsert: true },
    (err) => {
      if (err) console.info(err); 
      else await asyncFunction();
    }
  );

К этому:

await schema.findOneAndUpdate(
    { queryData },
    { updateData },
    { upsert: true },
    (err) => {
      if (err) console.info(err);
    }
  );
 if (success) await asyncFunction();

Используйте $addToSet вместо $push, это должно решить проблему. Я считаю, что есть проблема со структурой данных, используемой при создании «Модели» мангуста. Как мы знаем, push — это операция массива (которая допускает дублирование), в то время как addToSet может быть операцией Set (Sets не допускают дублирования).

$addToSet вместо $push позволил мне предотвратить дублирование записи в моем поле массива mongoDb пользовательского документа, подобного этому.

const blockUserServiceFunc = async(req, res) => {

let filter = {
    _id : req.body.userId
}

let update = { $addToSet: { blockedUserIds:  req.body.blockUserId  } };

await User.findOneAndUpdate(filter, update, (err, user) => {
    if (err) {
        res.json({
            status: 501,
            success: false,
            message: messages.FAILURE.SWW
        });
    } else {

        res.json({
            status: 200,
            success: true,
            message: messages.SUCCESS.USER.BLOCKED,
            data: {
                'id': user._id,
                'firstName': user.firstName,
                'lastName': user.lastName,
                'email': user.email,
                'isActive': user.isActive,
                'isDeleted': user.isDeleted,
                'deletedAt': user.deletedAt,
                'mobileNo': user.mobileNo,
                'userName': user.userName,
                'dob': user.dob,
                'role': user.role,
                'reasonForDeleting': user.reasonForDeleting,
                'blockedUserIds': user.blockedUserIds,
                'accountType': user.accountType
            }
        });

    }
}
).catch(err => {
    res.json({
        status: 500,
        success: false,
        message: err
    });
});

} Mongoose output

Другие вопросы по теме