Обновите один, вставьте, если он не существует, и измените его, только если условие истинно. MongoDB - PullRequest
0 голосов
/ 19 марта 2020

Я пишу программу на Node.js, используя MongoDB, и я хочу обновить базу данных при изменении определенных файлов. Внутри базы данных я храню mtime каждой записи базы данных и хочу сравнить ее с mtime файла, чтобы увидеть, устарела ли запись базы данных. Вот мой код:

function updateArticles(articles) {
    const dir = "articles";
    const files = fs.readdirSync(dir);

    files.forEach(
        (file) => {
            const fpath = path.join(dir, file);
            const fmtime = fs.statSync(fpath).mtimeMs;
            articles.updateOne(
                //refresh content and mtime if the text file has been modified
                {
                    _id: file,
                    mtime: {$lte: fmtime}
                },
                {
                    $set: {content: htmlifySync(fpath), mtime: Date.now(), title: titleSync(fpath)},
                    //btime is only set once
                    $setOnInsert: {btime: Date.now()}
                },
                {upsert: true}
            )
        });
}

Когда я устанавливаю {upsert: true}, я получаю ошибку дублирующего ключа. Когда я устанавливаю {upsert: false} новые записи не добавляются. Это работает, если я удаляю второе условие в своем запросе, но затем он запускает htmlifySync и titleSync для каждой записи, которая является дорогой и ненужной.

Я думаю, что проблема в том, что mongoDB пытается вставить новая запись, которая удовлетворяет обоим условиям запроса, что означает, что она пытается вставить новую запись с тем же идентификатором. Я хочу, чтобы mtime: {$lte: fmtime} был фильтром для обновления, но я не хочу, чтобы оно обновлялось обновлением.

Чтобы уточнить:

  • Если есть БД запись для файла:
    • Если файл был изменен с момента последнего обновления БД, я хочу обновить его
    • Если файл не был изменен с момента последнего обновления БД, который я хочу оставить без изменений
  • Если для файла нет записи в БД, я хочу создать одну

Ответы [ 2 ]

0 голосов
/ 20 марта 2020

Я понял, что действительно хочу избегать оценки htmlifySync для документов, которые не нуждаются в обновлении. Я думаю, что единственный способ сделать это - сначала findOne, а затем insertOne или updateOne как единственный способ остановить оценку htmlifySync и titleSync каждый раз, это вставить их в операторы if.

articles.findOne({_id: file}, {mtime: 1}).then(
    entry => {
        if (!entry) {
            articles.insertOne(
                {
                    _id: file,
                    mtime: Date.now(),
                    title: titleSync(fpath),
                    content: htmlifySync(fpath),
                    btime: Date.now()
                }
            ).then((res) => {
                console.log(res.res)
            })
        } else if (entry.mtime < fmtime) {
            console.log(file);
            articles.updateOne(
                {_id: file},
                {
                    $set: {
                        mtime: Date.now(),
                        title: titleSync(fpath),
                        content: htmlifySync(fpath)
                    }
                }
            ).then((res) => {
                console.log(res.res)
            })
        }
    }
)
0 голосов
/ 20 марта 2020

Операция обновления попытается вставить новый документ, если совпадений нет. Поскольку у вас не будет совпадения, если файл не был обновлен, он пытается вставить новый, вызывая ошибку.

Возможные решения:

  • Перехватите ошибку и проигнорируйте ее.
    Ошибка повторяющегося ключа означает, что данный файл уже существует в базе данных, но время файла не превышает сохраненное время, поэтому никаких действий не требуется.
  • Используйте условное присвоение в обновлении Если вы используя MongoDB 4.2, вы можете использовать выражения агрегации в обновлении.
articles.updateOne(
                {
                    _id: file
                },
                [{
                    $set: {
                            content: htmlifySync(fpath), 
                            mtime: {$cond:[{$lt:["$mtime",fmtime]},Date.now(),"$mtime"]} 
                            title: titleSync(fpath),
                            btime: {$cond:[{$eq:[{$type:"$btime"},"missing"]},Date.now(),"$btime"]}
                    }
                }],
                {upsert: true}
            )

При этом $cond используется только для установки btime, если он еще не существует, и только для mtime если сохраненное значение меньше fmtime и использует тот факт, что MongoDB фактически не будет обрабатывать запись, если обновление устанавливает те же значения, которые уже существуют.

...