使用arrayFilters更新MongoDB中的嵌套子文档

时间:2017-11-10 14:36:12

标签: arrays mongodb mongoose nested

我需要修改另一个数组中的数组内的文档。 我知道MongoDB不支持多个'$'同时迭代多个数组,但他们为此引入了 arrayFilters 。 请参阅:https://jira.mongodb.org/browse/SERVER-831

MongoDB的示例代码:

db.coll.update({}, {$set: {“a.$[i].c.$[j].d”: 2}}, {arrayFilters: [{“i.b”: 0}, {“j.d”: 0}]})
Input: {a: [{b: 0, c: [{d: 0}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Output: {a: [{b: 0, c: [{d: 2}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}

以下是文件的设置方式:

{
    "_id" : ObjectId("5a05a8b7e0ce3444f8ec5bd7"),
    "name" : "support",
    "contactTypes" : {
        "nonWorkingHours" : [],
        "workingHours" : []
    },
    "workingDays" : [],
    "people" : [ 
        {
            "enabled" : true,
            "level" : "1",
            "name" : "Someone",
            "_id" : ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
            "contacts" : [ 
                {
                    "_id" : ObjectId("5a05a8dee0ce3444f8ec5bda"),
                    "retries" : "1",
                    "priority" : "1",
                    "type" : "email",
                    "data" : "some.email@email.com"
                }
            ]
        }
    ],
    "__v" : 0
}

这是架构:

const ContactSchema = new Schema({
    data: String,
    type: String,
    priority: String,
    retries: String
});

const PersonSchema = new Schema({
    name: String,
    level: String,
    priority: String,
    enabled: Boolean,
    contacts: [ContactSchema]
});

const GroupSchema = new Schema({
    name: String,
    people: [PersonSchema],
    workingHours: { start: String, end: String },
    workingDays: [Number],
    contactTypes: { workingHours: [String], nonWorkingHours: [String] }
});

我需要更新联系人。这是我尝试使用arrayFilters:

Group.update(
    {},
    {'$set': {'people.$[i].contacts.$[j].data': 'new data'}},
    {arrayFilters: [
        {'i._id': mongoose.Types.ObjectId(req.params.personId)},
        {'j._id': mongoose.Types.ObjectId(req.params.contactId)}]},
    function(err, doc) {
        if (err) {
            res.status(500).send(err);
        }
        res.send(doc);
    }
);

该文档永远不会更新,我得到了这样的答复:

{
    "ok": 0,
    "n": 0,
    "nModified": 0
}

我做错了什么?

1 个答案:

答案 0 :(得分:2)

所以带有positional filtered $[<identifier>]arrayFilters选项确实可以与MongoDB 3.5.12以及MongoDB 3.6系列的当前版本候选版本中的开发版本系列一起正常工作释放。唯一的问题当然是使用中的“司机”还没有真正赶上这个。

重新审核我已经放在Updating a Nested Array with MongoDB上的相同内容:

  

注意有点讽刺的是,由于这是在.update()的“options”参数和类似方法中指定的,因此语法通常与所有最新的发行版驱动程序版本兼容。

     

然而,mongo shell并不是这样,因为在那里实现方法的方式(“具有讽刺意味的是为了向后兼容性”)arrayFilters参数不被内部方法识别和删除。解析选项,以便提供与以前的MongoDB服务器版本和“遗留”.update() API调用语法的“向后兼容性”。

     

因此,如果您想在mongo shell或其他“基于shell”的产品(特别是Robo 3T)中使用该命令,则需要3.6或更高版本的开发分支或生产版本的最新版本。

所有这些意味着.update()的当前“驱动程序”实现实际上“移除”了arrayFilters定义的必要参数。对于NodeJS,这将在驱动程序的3.x版本系列中解决,当然“mongoose”在该版本之后可能需要一些时间来实现它自己对更新的驱动程序的依赖性,这将不再“剥离”这样的行动。

然而,您仍然可以在支持的服务器实例上运行此操作,方法是回退到基本"update command"语法用法,因为这绕过了实现的驱动程序方法:

const mongoose = require('mongoose'),
      Schema = mongoose.Schema,
      ObjectId = mongoose.Types.ObjectId;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const contactSchema = new Schema({
  data: String,
  type: String,
  priority: String,
  retries: String
});

const personSchema = new Schema({
  name: String,
  level: String,
  priority: String,
  enabled: Boolean,
  contacts: [contactSchema]
});

const groupSchema = new Schema({
  name: String,
  people: [personSchema],
  workingHours: { start: String, end: String },
  workingDays: { type: [Number], default: undefined },
  contactTypes: {
    workingHours: { type: [String], default: undefined },
    contactTypes: { type: [String], default: undefined }
  }
});

const Group = mongoose.model('Group', groupSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

    // Clean data
    await Promise.all(
      Object.entries(conn.models).map(([k,m]) => m.remove() )
    );

    // Create sample

    await Group.create({
      name: "support",
      people: [
        {
          "_id": ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
          "enabled": true,
          "level": "1",
          "name": "Someone",
          "contacts": [
            {
              "type": "email",
              "data": "adifferent.email@example.com"
            },
            {
              "_id": ObjectId("5a05a8dee0ce3444f8ec5bda"),
              "retries": "1",
              "priority": "1",
              "type": "email",
              "data": "some.email@example.com"
            }
          ]
        }
      ]
    });

    let result = await conn.db.command({
      "update": Group.collection.name,
      "updates": [
        {
          "q": {},
          "u": { "$set": { "people.$[i].contacts.$[j].data": "new data" } },
          "multi": true,
          "arrayFilters": [
            { "i._id": ObjectId("5a05a8c3e0ce3444f8ec5bd8") },
            { "j._id": ObjectId("5a05a8dee0ce3444f8ec5bda") }
          ]
        }
      ]
    });

    log(result);

    let group = await Group.findOne();
    log(group);

  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})()

由于它将“命令”直接发送到服务器,我们看到预期的更新确实发生了:

Mongoose: groups.remove({}, {})
Mongoose: groups.insert({ name: 'support', _id: ObjectId("5a06557fb568aa0ad793c5e4"), people: [ { _id: ObjectId("5a05a8c3e0ce3444f8ec5bd8"), enabled: true, level: '1', name: 'Someone', contacts: [ { type: 'email', data: 'adifferent.email@example.com', _id: ObjectId("5a06557fb568aa0ad793c5e5") }, { _id: ObjectId("5a05a8dee0ce3444f8ec5bda"), retries: '1', priority: '1', type: 'email', data: 'some.email@example.com' } ] } ], __v: 0 })
{ n: 1,
  nModified: 1,
  opTime:
   { ts: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
     t: 24 },
  electionId: 7fffffff0000000000000018,
  ok: 1,
  operationTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
  '$clusterTime':
   { clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
     signature: { hash: [Object], keyId: 0 } } }
Mongoose: groups.findOne({}, { fields: {} })
{
  "_id": "5a06557fb568aa0ad793c5e4",
  "name": "support",
  "__v": 0,
  "people": [
    {
      "_id": "5a05a8c3e0ce3444f8ec5bd8",
      "enabled": true,
      "level": "1",
      "name": "Someone",
      "contacts": [
        {
          "type": "email",
          "data": "adifferent.email@example.com",
          "_id": "5a06557fb568aa0ad793c5e5"
        },
        {
          "_id": "5a05a8dee0ce3444f8ec5bda",
          "retries": "1",
          "priority": "1",
          "type": "email",
          "data": "new data"            // <-- updated here
        }
      ]
    }
  ]
}

正确“now” [1] “现成的”驱动程序实际上并未实现.update()或其他实现的对应物与实际传递必要的arrayFilters参数兼容的方式。因此,如果您正在“玩”开发系列或发布候选服务器,那么您真的应该准备好与“前沿”和未发布的驱动程序一起工作。

但是你可以在任何驱动程序中以正确的形式证明这一点,不会改变发出的命令。

  

[1] 截至2017年11月11日撰写时,MongoDB没有“官方”版本或实际实施的受支持驱动程序这个。生产使用应基于服务器的官方版本和仅支持的驱动程序。