如果在更新时属性的值为null,则不应将该属性添加到记录中

时间:2017-03-14 10:34:53

标签: javascript node.js mongodb mongoose

假设我有一个像这样的猫鼬模式:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var testSchema = new Schema({
    name: {type: String, required: true},
    nickName: {type: String}
});

var Test = module.exports = mongoose.model('Test', testSchema);

我使用变量Test声明了CRUD操作的方法。从那一个这样的方法是更新,其定义如下:

module.exports.updateTest = function(updatedValues, callback) {

    console.log(updatedValues); //this output is shown below

    Test.update(
        { "_id": updatedValues.id },
        { "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
        { multi: false },
        callback
    );

};

现在,我在节点路由器中使用此方法,如下所示:

router.put('/:id', function(req, res, next) {

    var id = req.params.id,
    var name = req.body.name,
    var nickName = req.body.nickName

    req.checkBody("name", "Name is required").notEmpty();

    var errors = req.validationErrors();

    if(errors) { ........ }
    else {

        var testToUpdate = new Test({
            _id: id,
            name: name,
            nickName: nickName || undefined
        });

        Test.updateTest(testToUpdate, function(err, result) {
            if(err) { throw(err); }
            else { res.status(200).json({"success": "Test updated successfully"}); }
        });

    }
});

现在,如果我在数据库中保存一条新记录然后在数据库中看到它,那么它看起来像是:

{
    "_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id 
    "name" : "firstTest",
    "__v" : 0
}

现在,如果我在不更改任何内容的情况下更新同一文档,那么如果我查看数据库中的相同记录,那么我得到:

{
    "_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id 
    "name" : "firstTest",
    "__v" : 0,
    "nickName" : null
}

你能看到nickName被设置为null吗?我不希望它像这样工作。我希望如果我的属性为null,那么该属性不应该包含在记录中。

如果你还记得,我有控制台在更新之前记录了updatedValues。 (参见第二个代码块)。所以,这是记录的值:

{
    "_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id 
    "name" : "firstTest"
}

我不知道为什么,但是在记录的值中没有nickName,然后在更新后我得到nickName: null。我认为,问题在于第二个代码块。你能检查一下吗?

注意:

实际上我的架构中的字段比我指定的字段多得多。某些字段也引用其他记录。

7 个答案:

答案 0 :(得分:2)

您可以通过在 update 方法中将 runValidators 选项设置为 true 来阻止此类文档在 MongoDB 中更新。

例如:

  module.exports.updateTest = function(updatedValues, callback) {

    Test.update(
      { "_id": updatedValues.id },
      { "$set" : { 
        "name" : updatedValues.name, "nickName" : updatedValues.nickName } ,
      },
      { multi: false, runValidators: true },
      callback
    );
  };

此外,您还可以将选项 omitUndefined 设置为 true 以防止未定义的值被反映。

例如:

  module.exports.updateTest = function(updatedValues, callback) {

    Test.update(
      { "_id": updatedValues.id },
      { "$set" : { 
        "name" : updatedValues.name, "nickName" : updatedValues.nickName } ,
      },
      { multi: false, runValidators: true, omitUndefined: true },
      callback
    );
  };

答案 1 :(得分:1)

查看您的代码,您的更新方法中存在一个问题,它无法帮助您获得所需的结果。

这是您更新代码:

module.exports.updateTest = function(updatedValues, callback) {

    console.log(updatedValues); //this output is shown below

    Test.update(
        { "_id": updatedValues.id },
        { "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
        { multi: false },
        callback
    );

};

问题出在$set部分。

在mongodb中,您只需为其分配undefined即可取消设置文档中的字段。您应该使用$unset运算符。

所以你的更新代码应该是这样的:

module.exports.updateTest = function(updatedValues, callback) {

    console.log(updatedValues); //this output is shown below
    const operators = {$set: {name: updatedValues.name}};
    if (updatedValues.nickName) {
        operators.$set.nickName = updatedValues.nickName;
    } else {
        operators.$unset = {nickName: 1};
    }
    Test.update(
        { "_id": updatedValues.id },
        operators,
        { multi: false },
        callback
    );

};

请注意,如果字段中已存在该字段,则使用$unset是删除字段的基础。

答案 2 :(得分:1)

我不会这样写,但我会告诉你代码失败的原因。

问题是你的$ set block

您选择将值专门设置为传入的更新对象。如果值为undefined,则强制mongo将其设置为null。

这是问题

例如,在DB中:

{ "_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), "name" : "firstTest", "nickname": "jack", "__v" : 0 }

如果你传入testToUpdate = { name: 'foo' },你最终会得到 Test.update({ ... }, { $set: { name: 'foo', nickname: undefined }}

因为您从参数中获取updatedValues.nickname并且没有定义

你想要的是什么 Test.update({ ... }, { $set: updatedValues },转换为Test.update({ ... }, { $set: { name: 'foo' } }

您不再为昵称提供密钥,因此不会将其设置为undefined / null。

我会使用mongoose插件而不用担心手动将字段一直传递给模型

请参阅https://github.com/autolotto/mongoose-model-update

您可以定义可更新字段,然后就可以执行

model.update(req.body)并且不用担心这一切

即使您不想使用该插件,您仍然可以执行

Test.findByIdAndUpdate(id, { name, nickname }, callback)

答案 3 :(得分:1)

正如其他答案中所指出的那样,问题确实是您的 $set 部分,但是使用 if 条件并不是做到这一点的最佳方法。如果有多个字段,您可能需要多个 if 条件,甚至需要一个单独的函数来处理这个问题。

Mongoose 提供了一个非常好的解决方案:{omitUndefined: 1},它不会更新查询中的所有未定义值。

答案 4 :(得分:0)

问题是您仍在文档中添加nickname属性 - 您只需将其设置为undefined,但与您认为的相反,不是同样没有设置它。

如果未定义昵称属性,则根本不包括昵称属性,您可以这样做:

module.exports.updateTest = function(updatedValues, callback) {

    const set = { "name": updatedValues.name };

    if (updatedValues.nickName) {
        set["nickName"] = updatedValues.nickName;
    }

    Test.update({ "_id": updatedValues.id }, { "$set": set}, { multi: false },
        callback
    );

};

答案 5 :(得分:0)

正如您在文档

中所述的查询中使用$set一样
  

$ set运算符用指定的值替换字段的值。

因此,您强制将此处的null未定义值设置为字段。您可以将required : true设置为架构中的nickName字段,或尝试传递JS对象而不是编写原始查询,如: 而不是:

Test.update(
        { "_id": updatedValues.id },
        { "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
        { multi: false },
        callback
    );

尝试做:

var data = {  name : updatedValues.name };
if(updatedValues.nickName){
     data.nickName = updatedValues.nickName;
}
Model.update({ "_id": updatedValues.id }, data ,options, callback)

检查Mongoose documentation中的此链接,了解有关该方法的更多信息。

答案 6 :(得分:0)

我找到的最简单的方法是使用lodash.pickby,您只需传递body(或与此相关的任何对象),它将删除所有的undefinednull字段和键