Mongoose findOneAndUpdate Upsert _id null?

30

我希望有一个单一的方法,可以创建或更新策略文档。我尝试搜索和尝试不同的技术,比如这种方法,但我的文档中却有一个空的_id字段。使用findByIdAndUpdate也会产生类似的影响。

我看到一个文档被插入到集合中,但_id字段为空:

exports.savePolicy = function (plcy, callback) {
    console.log('priority is : ' + plcy.priority)
    try {
        var policy = new Policy(plcy);
        var query = {_id: plcy._id};  //this may be null
        var update = {
            name: plcy.name || defaults.policyDefaults.name,
            longDescription: plcy.longDescription || defaults.policyDefaults.longDescription,
            shortDescription: plcy.shortDescription || defaults.policyDefaults.shortDescription,
            priority: plcy.priority, colorHex: plcy.colorHex || defaults.policyDefaults.colorHex,
            settings: plcy.settings || [],
            parentPolicyId: plcy.parentPolicyId || null
        }

        Policy.findOneAndUpdate(query, update, {upsert: true}, function (err, data) {
            callback(err, data);
        });

    } catch (e) {
        log.error('Exception while trying to save policy: ' + e.message);
        callback(e, null);
    }

当不是更新时,有什么方法可以使_id不为空吗?


1
你最终解决了这个问题吗?我也遇到了同样的问题。我最后手动检查了 _id,然后根据情况调用 findOneAndUpdate 进行更新或 create() 创建新记录。 - Trent
9个回答

24

null在MongoDB中是一个有效的_id值,因此如果您不希望在新文档中使用它,则必须确保将null值替换为新的ObjectIDquery中:

var query = {_id: plcy._id};
if (!query._id) {
    query._id = new mongoose.mongo.ObjectID();
}

// the rest stays the same...

1
我们使用ObjectIds来推断创建时间戳。是不是最好由服务器端创建ObjectIds而不是客户端创建它们? - dhaundy

6

我曾经遇到过同样的问题,但无法找到解决方法。最终我自己编写了一个upsert方法,如下所示...

var upsert = function(model, data, f){
  if (!data._id) {
    model.create(data, f);
  } else {
    var id = data._id;
    delete data._id;
    model.findOneAndUpdate({_id: id}, data, f);
  }
}

这使得我可以用一行代码调用它来处理任何一个模型...

upsert(Team, team, f);

可能有更好的方法,但这对我来说是有效的。我可以进行更新和插入操作,而且在插入时不会得到空的 _id。


3
可以使用类似以下的方式解决这个问题: Model.findOneAndUpdate({ _id: id || Mongoose.Types.ObjectId() }, {$set: attrs}, { upsert: true, new: true }) - Diego Dias

4

如果有ID,则更新文档最简单的方法是,如果没有ID,则创建新文档:

Model.findOneAndUpdate({ _id: id ?? new mongoose.Types.ObjectId() }, updates, { upsert: true });


注意使用 空值合并运算符(??),它允许0作为有效id。 updates 对象不需要包含一个 id 属性,因为 mongoose 会 自动添加 它:

如果没有文档与过滤器匹配,MongoDB 将通过将过滤器和更新结合起来插入一个文档,如下所示。


3

感谢JohnnyHK提供的有用答案。由于这种情况经常出现,所以我想到了一个一行代码的解决方法:

query = args._id ? { _id: args._id } : { _id: new ObjectId() };

它依赖于以下要求:
const ObjectId = require('mongodb').ObjectID;

改进后的代码如下: query = {_id: args._id !=null ? args._id : new ObjectId() } - Collins USHI

1

这可能会有帮助(来自文档):

如果您在查询参数或替换文档中指定了_id字段,MongoDB将在插入的文档中使用该_id字段。

因此,当您编写以下代码时:

model.findOneAndUpdate({_id: id}, data, f);

如果idnull,它将插入一个文档,并将null作为id。

1

我没有使用Mongoose,但在MongoDB中遇到了类似的问题。对于Upsert操作,当插入新对象时,MongoDB会将_id设置为null

我调用了:

findOneAndUpdate({my_id:'my_unique_id'}, obj, {upsert: true})

obj._idundefined 时出现了问题。

问题是 _id 出现在了 Object.keys(obj) 的键列表中。我发现我正在赋值 obj._id = some_variable,其中 some_variableundefined,这导致 _id 出现在键列表中。

我通过在 upsert 之前调用以下解决方法来解决这个问题:

if (_.isUndefined(obj._id)) {
  delete obj._id;
}

0
由于在JavaScript中我们现在可以在解构对象时添加默认属性,因此在检查文档是否存在的情况下,我发现这是最简单的方法之一,可以通过现有的“_id”查询或在同一操作中创建一个新的“_id”,从而避免了空ID问题:
// someController.js使用POST路线
async function someController(req, res) {
  try {
  const {
    // the default property will only be used if the _id doesn't exist
    _id: new mongoose.Types.ObjectId(),
    otherProp,
    anotherProp
  } = req.body;
  const createdOrUpdatedDoc = await SomeModel.findOneAndUpdate(
    {
      _id
    },
    {
      otherProp,
      anotherProp
    },
    {
      new: true,
      upsert: true
    }
  ).exec();
  return res.json(201).json(createdOrUpdatedDoc);
  } catch (error) {
    return res.json(400).send({
      error,
      message: "Could not do this"
    })
  }
}

-2

-3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接