【问题标题】:If value of a property is null when updating then that property should not be added to the record如果更新时属性的值为 null,则不应将该属性添加到记录中
【发布时间】:2017-08-04 15:33:24
【问题描述】:

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

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 吗?我不希望它像这样工作。我希望如果我的属性为空,那么该属性不应包含在记录中。

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

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

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

注意:

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

【问题讨论】:

  • 似乎你想对$set..stackoverflow.com/questions/286141/…的值使用这些技术之一
  • 浏览答案和cmets,这似乎不是您的真实问题,如果属实,请更新您的问题,以便我们为您提供帮助。

标签: javascript node.js mongodb mongoose


【解决方案1】:

我不会这样写,但我会告诉你为什么你的代码会失败。

问题是你的 $set 块

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

问题来了

例如,在数据库中:

{
    "_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 插件,而不必担心手动将字段一直传递给您的模型(请参阅 github.com/autolotto/mongoose-model-update

  • 您可以定义可更新的字段,然后您只需执行model.update(req.body) 而无需担心所有这些
  • 即使您不想使用该插件,您仍然可以使用Test.findByIdAndUpdate(id, { name, nickname }, callback)

【讨论】:

  • 感谢您的精彩回答。
【解决方案2】:

您可以通过在更新方法中将 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
    );
  };

【讨论】:

    【解决方案3】:

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

    Mongoose 提供了一个非常好的选项来处理这个问题:{omitUndefined: 1},它不会更新您查询中的所有未定义值。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        当您在文档中说明的查询中使用$set

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

        因此,您强制将此处为 null 的未定义值设置为该字段。您可以针对架构中的nickName 字段设置required : true,或者尝试传递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 中的此链接以获取有关该方法的更多信息。

        【讨论】:

        • 我已尝试使用您提供的答案。但它给了我一个错误,说:CastError: Cast to string failed for value {............},花括号中是我的对象。
        • 好的,我在数据中缺少.name。现在,我的架构中还有一些分层对象,如下所示:details: { mailing: { name: String, address: string }, contact: { mobile: string, email: email }}。上面的对象作为示例显示。如何更新?
        • 我假设您想知道如何更新嵌套值,理想情况下它也应该适用于嵌套对象。因为,在 mongo 中,要访问嵌套对象,您需要以 .(dot) 运算符的形式进行。
        • 我已经用点运算符尝试过,但我得到一个错误:无法设置未定义的地址。该错误非常具有描述性。所以,我尝试设置data.details = { }; data.details.mailing = { }; data.details.mailing.address = updatedValues.address; 现在又遇到另一个错误:不能同时设置邮件和地址。
        • @Vishal 你必须先定义内部对象。而且我怀疑您是否可以将多个参数作为一个参数更新为 $set
        【解决方案6】:

        查看您的代码,您的更新方法有问题,无法帮助您获得所需的结果。

        这是你更新的代码:

        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 是删除该字段的基础。

        【讨论】:

        • 实际上,我的架构中的字段比我指定的要多得多。一些字段也引用其他记录。我仍然尝试了您的答案,但更新时出现错误,提示:Cast Error: Cast to string failed for value "{.......显示对象。
        • 我的架构中还有一些分层对象,如下所示:详细信息:{ 邮件:{ 姓名:字符串,地址:字符串},联系人:{手机:字符串,电子邮件:电子邮件}}。上面的对象显示为示例。我该如何更新它?
        • 请忽略我的第一条评论,因为它已解决,这是我的打字错误。
        【解决方案7】:

        问题是您仍然在文档上添加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
            );
        
        };
        

        【讨论】:

        • 实际上,我的架构中的字段比我指定的要多得多。一些字段也引用其他记录。我仍然尝试了您的答案,但在更新时出现错误,提示:Cast Error: Cast to string failed for value "{ .......... }",大括号中我的显示对象。
        • 我的架构中还有一些分层对象,如下所示:详细信息:{ 邮件:{ 姓名:字符串,地址:字符串},联系人:{手机:字符串,电子邮件:电子邮件}}。上面的对象显示为示例。我该如何更新它?
        • 请忽略我的第一条评论,因为它已解决,这是我的打字错误。
        • @Vishal 好的,这是否意味着我的回答有帮助?
        • 我不知道你的答案是否有效,因为我收到错误说无法设置未定义的地址。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-06
        • 2019-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多