Nodejs mongoose深层修改问题

Nodejs mongoose深层修改问题

各位大神好,好久没写点什么东西了,最近也是cnode社区不知道咋的了都登录不进去,今天总算能回到这里,今天遇到这样的一个问题,发出来咨询下各位。 mongoose提供的schema/model/document想必大家都不陌生,model有一个update方法,现假设我的schema如下

//假设Schema对象已经从Mongoose的api中提取出来
var schema = new Schema({
    baseinfo:{
        name:String,
        age:Number
    },
    status:Number
});

如果数据库里已经存了这样的数据

{baseinfo:{name:‘loong’,age:20},status:0} 我现在要做这样的事情,只修改年龄。 我知道modelupdate方法支持mongodb原生的api,例如

    //代码1
    schemaModel.update({name:'loong'},{$set:{baseinfo:{age:26}}});

$setapi原本是支持有值覆盖/没值不改这一套模式(我称之为设置模式)的,即**$set后传入什么就修改什么,如果不传世不会被修改了,代码1中我没有传入status的修改,因此执行后在数据库的数据仍然是0。

但问题就来了,$set支持这的这模式对于层级只有一层的status很有用,但是对于有2个层级的baseinfo来说就不是这样的了。代码1最终运行的结果是age确实改为了26,但name属性却没有了。他的理解是将baseinfo:{age:26}这个键值对对应的覆盖(我称之为覆盖模式**)到实体上,理所当然的没有了name咯。 当然我在问问前我肯定是要做一番研究的,我从来不会做那种自己不研究就上来问问题的。那么我要研究的课题是:如何让mongoosemodel支持多层级属性设置模式。在上例中,按照我所研究的课题就是,传什么,改什么,对于深层对象也做判断,永远没有覆盖。 于是,我决定采用深层**$set**,看是否能有所效果:

    //代码2
    schemaModel.update({name:'loong'},{$set:{baseinfo:{$set:{age:26}}}});

这时候,我在baseinfo后,age前又加了个**$set**,因为我觉得**$set**只查找一层,那么设置2个应该就能查找2层咯,但是结果返回错误,这时不被允许的。

再次查找api,尝试了使用如下的方式:

    //代码3
    schemaModel.update({name:'loong'},{$set:{‘baseinfo.age’:26}});

这时候是可以的,但是问题就来了:1.‘baseinfo.age’这样的写法是需要从json中转换的,麻烦,2.我要更新baseinfo下的多个数据,就要在这里写很多了。

有没有既不需要转换,同时又能解决多个数据的方式呢? 我再次看了看api,尝试了如下的方法:

    //代码4
    schemaModel.update({name:'loong'},{$set:{‘baseinfo.$’:{age:26}}});

这次代码是通过了,但是貌似并没有实现修改,这我就纳闷了,为什么会这样呢?查看了api,发现,对.$理解错了,他是针对数组的操作,如果有一个数组的匹配项,则做该项的更新。

ok,尝试了很多,发现都没有得到自己想要的结果,那我们就退一步吧,既然model无法实现,那么document能否实现呢?我的尝试仍然继续:

    //代码5
    schemaModel.findOne({name:'loong'},function(err,doc){
        doc.set({baseinfo:{age:26}});
        doc.save();
    });

一运行,这次真的成功了,name属性没有消失,而age属性也成功的修改了。

到此实验已经完成,我退而求其次的找到了解决问题的方法。但是我仍然很好奇,希望能有人提供线索帮忙解决以下2个问题: 1.能不能让model也能做到多层次设置模式,而且要代码简单。 2.代码5如果baseinfo中还有多层的,不知道会不会出现类似代码1的问题,虽然实际中不会设置这么深的层次,但是还是作为一个问题,暂时没时间研究了,希望能有人测一下。


8 回复

Nodejs Mongoose 深层修改问题

各位大神好,好久没写点什么东西了,最近也是CNode社区不知道咋的了都登录不进去,今天总算能回到这里。今天遇到这样一个问题,发出来咨询下各位。

mongoose 提供的 schema/model/document 相信大家都不陌生,model 有一个 update 方法。现假设我的 schema 如下:

// 假设 Schema 对象已经从 Mongoose 的 api 中提取出来
var schema = new Schema({
    baseinfo: {
        name: String,
        age: Number
    },
    status: Number
});

如果数据库里已经存了这样的数据 {baseinfo:{name:'loong',age:20},status:0},我现在要做这样的事情,只修改年龄。

我知道 modelupdate 方法支持 MongoDB 原生的 API,例如:

// 代码1
schemaModel.update({name:'loong'}, {$set: {baseinfo: {age: 26}}});

$set API 原本是支持有值覆盖/没值不改这一套模式(我称之为 设置模式)的,即 $set 后传入什么就修改什么,如果不传世不会被修改了。代码1中我没有传入 status 的修改,因此执行后在数据库的数据仍然是 0

但问题就来了,$set 支持这种的这模式对于层级只有一层的 status 很有用,但是对于有2个层级的 baseinfo 来说就不是这样的了。代码1最终运行的结果是 age 确实改为了 26,但 name 属性却没有了。它的理解是将 baseinfo:{age:26} 这个键值对对应的覆盖(我称之为 覆盖模式)到实体上,理所当然的没有了 name 咯。

当然我在问问前我肯定是要做一番研究的,我从来不会做那种自己不研究就上来问问题的。那么我要研究的课题是:如何让 mongoosemodel 支持 多层级 属性 设置模式。在上例中,按照我所研究的课题就是,传什么,改什么,对于深层对象也做判断,永远没有覆盖。

于是,我决定采用深层 $set,看是否能有所效果:

// 代码2
schemaModel.update({name:'loong'}, {$set: {baseinfo: {$set: {age: 26}}}});

这时候,我在 baseinfo 后,age 前又加了个 $set,因为我觉得 $set 只查找一层,那么设置2个应该就能查找2层咯,但是结果返回错误,这时不被允许的。

再次查找 API,尝试了使用如下的方式:

// 代码3
schemaModel.update({name:'loong'}, {$set: {'baseinfo.age': 26}});

这时候是可以的,但是问题就来了:1. 'baseinfo.age' 这样的写法是需要从 JSON 中转换的,麻烦;2. 我要更新 baseinfo 下的多个数据,就要在这里写很多了。

有没有既不需要转换,同时又能解决多个数据的方式呢?

我再次看了看 API,尝试了如下的方法:

// 代码4
schemaModel.update({name:'loong'}, {$set: {'baseinfo.$': {age: 26}}});

这次代码是通过了,但是貌似并没有实现修改。这我就纳闷了,为什么会这样呢?查看了 API,发现,对 .$ 理解错了,他是针对数组的操作,如果有一个数组的匹配项,则做该项的更新。

OK,尝试了很多,发现都没有得到自己想要的结果,那我们就退一步吧,既然 model 无法实现,那么 document 能否实现呢?我的尝试仍然继续:

// 代码5
schemaModel.findOne({name:'loong'}, function(err, doc) {
    doc.set({baseinfo: {age: 26}});
    doc.save();
});

一运行,这次真的成功了,name 属性没有消失,而 age 属性也成功地修改了。

到此实验已经完成,我退而求其次的找到了解决问题的方法。但是我仍然很好奇,希望能有人提供线索帮忙解决以下两个问题:

  1. 能不能让 model 也能做到 多层次设置模式,而且要代码简单。
  2. 代码5 如果 baseinfo 中还有多层的,不知道会不会出现类似代码1的问题,虽然实际中不会设置这么深的层次,但是还是作为一个问题,暂时没时间研究了,希望能有人测一下。

希望这些问题能得到解答!


我觉得model直接改得好处,少了一步save()!但是第二种更自然点,找到这个文件,直接对文件下的key赋值,在保存下,也很简单啊!即使多层,可以一直.下去(数组除外)

schemaModel.findOne({name:'loong'},function(err,doc){
        doc.baseinfo.age=26;
        doc.save();
    });

你的问题应该是直接对文档里的某一个内嵌文档的属性做修改,而不破坏整个文档吧,我一般用原生的mongo-native,展示给你看下:

[root[@centos6998](/user/centos6998) ~]# mongo
MongoDB shell version: 2.2.2
connecting to: test
db.testcol.insert({info:{name:123,age:123,sex:"male"},id:1,weight:0})
> db.testcol.find()
{ "_id" : ObjectId("50de94734af5ea426ff2f77b"), "info" : { "name" : 123, "age" : 123, "sex" : "male" }, "id" : 1, "weight" : 0 }
> db.testcol.update({id:1},{$set:{"info.name":0}})
> db.testcol.find()
{ "_id" : ObjectId("50de94734af5ea426ff2f77b"), "info" : { "name" : 0, "age" : 123, "sex" : "male" }, "id" : 1, "weight" : 0 }

火钳刘明

var age, data, id, name;

id = req.query.id;

name = req.query.name;

age = req.query.age;

data = {
  name:  name,
  age: age
};

model.findOneAndUpdate({
  _id: id
}, data, null, function(err, results) {
  if (err) {
    return res.json(500, err);
  }
  #在此操作代码
});

用点语法,

//你的代码1
schemaModel.update({name:'loong'},{$set:{"baseinfo.age":26}});
//字段名加引号保险些,有时候不加引号会有问题,也搞不清楚什么时候有问题

另外再问你个问题啊,那个mongoose链接在整个项目里只可以有一个链接,那如果项目太大,进行数据操作的地方太多,那怎么办,虽然schema什么的都可以在外面定义,但是db.once(“open”,function(){}),所有的数据操作都在回调里,那会很乱的吧。。。有什么比较官方的解决办法吗?

遇到了同样的问题,目前用的是,不知道楼主找到好方式没有

  await UserInfoModel.findOneAndUpdate({ userId }, {
    $set: {
      'wxInfo.nickName': nickName,
      'wxInfo.avatarUrl': avatarUrl,
      'wxInfo.gender': gender,
      'wxInfo.city': city,
      'wxInfo.province': province,
      'wxInfo.country': country,
      'wxInfo.language': language,
      updateTimeUtc: Date.now(),
    }
  });

要实现深层次的 $set 操作,并且保持原有的结构不变,可以使用 lodash 或者 deepmerge 这样的库来帮助进行深层对象的合并操作。以下是一个示例,展示了如何使用 deepmerge 库来处理深层次的对象更新。

首先,安装 deepmerge 库:

npm install deepmerge

然后,你可以这样更新你的文档:

const mongoose = require('mongoose');
const deepmerge = require('deepmerge');

// 假设Schema对象已经从Mongoose的api中提取出来
const schema = new mongoose.Schema({
    baseinfo: {
        name: String,
        age: Number
    },
    status: Number
});

const schemaModel = mongoose.model('User', schema);

async function updateAge() {
    try {
        const user = await schemaModel.findOne({ name: 'loong' });

        if (user) {
            const updatedBaseinfo = deepmerge(user.baseinfo, { age: 26 });
            user.baseinfo = updatedBaseinfo;
            await user.save();
            console.log('User updated successfully:', user);
        } else {
            console.log('User not found');
        }
    } catch (err) {
        console.error('Error updating user:', err);
    }
}

updateAge();

在这个示例中,我们使用 deepmerge 库来合并现有的 baseinfo 对象和新的更新对象。这样可以确保不会丢失原有的 name 字段,也不会覆盖整个 baseinfo 对象。

关于你的两个问题:

  1. 目前 Mongoose 的 model.update 方法并不直接支持多层次的 $set 操作,但可以通过这种方式来实现。
  2. 使用 doc.setdoc.save 的方式可以在一定程度上避免覆盖原有字段,但如果 baseinfo 中有更多层级,依然需要使用类似的方法来处理。

这种方法不仅简洁,而且可以处理任意深度的对象更新。

回到顶部