更新一个,如果不存在则插入,仅当条件为真时才修改MongoDB

Update one, insert if not exists and modify only when a condition is true MongoDB

我正在使用 MongoDB 在 Node.js 中编写程序,我想在某些文件被修改时更新数据库。在数据库中,我存储了每个数据库条目的 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} 时,不会添加新条目。如果我删除查询中的第二个条件,它会起作用,但是对于每个昂贵且不必要的条目,它是 运行 htmlifySynctitleSync

我认为问题在于 mongoDB 正在尝试插入满足两个查询条件的新条目,这意味着它会尝试插入具有相同 ID 的新条目。我希望 mtime: {$lte: fmtime} 成为更新的过滤器,但我不希望更新使它成为现实。

澄清一下:

如果没有匹配项,更新操作将尝试插入新文档。如果文件没有更新,您将没有匹配项,它会尝试插入一个新文件,从而触发错误。

可能的解决方案:

  • 捕获错误并忽略它
    重复键错误表示给定文件已存在于数据库中,但文件时间不大于存储时间,因此无需采取任何措施。
  • 在更新中使用条件赋值 如果你使用的是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,并且仅在存储值小于fmtime时设置mtime,并且利用这样一个事实,即如果更新设置已存在的相同值,MongoDB 将不会实际处理写入。

我意识到我真正想避免的是评估 htmlifySync 不需要更新的文档。我认为唯一的方法是首先 findOne 然后 insertOneupdateOne 作为停止每次评估 htmlifySynctitleSync 的唯一方法就是把它们放在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)
            })
        }
    }
)