首页 > 解决方案 > MongoDB - 检查 $addToSet 是否重复

问题描述

我正在使用 MongoDB 为我的应用程序创建类似的功能。我最近偶然发现了一个问题。

我喜欢的代码:

async function like(articleId, userId) {
const db = await makeDb();
return new Promise((resolve, reject) => {
  const result = db.collection(collectionName).findOneAndUpdate(
    { articleId: ObjectID(articleId) },
    {
      $setOnInsert: {
        articleId: ObjectID(articleId),
        creationDate: new Date()
      },
      $addToSet: { likes: ObjectID(userId) }
    },
    { upsert: true }
  );
  resolve(result);
});

它能做什么:

  1. 如果集合中没有文档,则创建一个并填写所有字段
  2. 如果存在文档,则仅更新likes具有新用户 ID 的数组

这可以按预期工作并且没有问题,这要归功于$addToSe我没有任何重复等。

然而,在上面的代码被执行并result返回之后,我需要做一些额外的事情,为了让它工作,我需要知道 Mongos 是否$addToSet确实改变了任何东西。

简而言之:

  1. 如果新的 userId 已添加到likes数组中,我想做点什么
  2. 如果 userId 已经在likes数组中并且$addToSet没有改变任何我不想采取任何行动的东西。

有没有办法区分是否$addToSet做了某事?

结果的电流console.log()如下所示:

  { 
    lastErrorObject: { n: 1, updatedExisting: true },
    value: { _id: 5e2c47c57bb5183d80dce14f,
             articleId: 5da4d1365217baf52fbcd76a,
             creationDate: 2020-01-25T13:51:01.928Z,
             likes: [ 5d750b677d8edfc08af8a527 ] 
    },
    ok: 1 
  }

我想到的一件事(但在性能方面非常糟糕)是likes在更新之前比较数组是否包含用户 ID 和 javascriptincludes方法。

有更好的想法吗?

标签: javascriptnode.jsmongodbmongodb-query

解决方案


我找到了解决方案!

99% 的功劳归于Neil Lunn这个答案: https ://stackoverflow.com/a/44994382/2175228

它让我走上了正确的道路。我放弃了这种findOneAndUpdate方法,转而采用了这种updateOne方法。查询参数等一切都保持不变,只是方法名称改变了,显然这个方法的结果类型。

updateOne方法的问题在于它不返回已更新的对象(或旧对象)。但它确实返回了upsertedId在我的情况下我唯一需要的东西。

所以最后你得到的是一个很长的CommandResult对象,它的开头是:

 CommandResult {
  result: { n: 1, nModified: 0, ok: 1 },
  (...)
 }

但是当我仔细观察时,我注意到该对象具有我一直需要的那些字段:

  modifiedCount: 0,
  upsertedId: null, // if object was inserted, this field will contain it's ID
  upsertedCount: 0,
  matchedCount: 1 

所以你需要做的就是从CommandResult对象中取出你需要的部分,然后继续;)

更改后我的代码:

async function like(articleId, userId) {
    const db = await makeDb();
    return new Promise((resolve, reject) => {
      db.collection(collectionName)
        .updateOne(
          { articleId: ObjectID(articleId) },
          {
            $setOnInsert: {
              articleId: ObjectID(articleId),
              creationDate: new Date()
            },
            $addToSet: { likes: ObjectID(userId) }
          },
          { upsert: true }
        )
        .then(data => {
          // wrap the content that You need
          const result = {
            upsertedId: data.upsertedId,
            upsertedCount: data.upsertedCount,
            modifiedCount: data.modifiedCount
          };

          resolve(result);
        });
    });
  }

希望这个答案能帮助将来遇到类似问题的人:)


推荐阅读