使用 arrayFilters 更新 MongoDB 中的嵌套子文档 [英] Update nested subdocuments in MongoDB with arrayFilters

查看:40
本文介绍了使用 arrayFilters 更新 MongoDB 中的嵌套子文档的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need to modify a document inside an array that is inside another array. I know MongoDB doesn't support multiple '$' to iterate on multiple arrays at the same time, but they introduced arrayFilters for that. See: https://jira.mongodb.org/browse/SERVER-831

MongoDB 的示例代码:

MongoDB's sample code:

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 的方法:

I need to update a contact. This is what I tried using 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);
    }
);

文档永远不会更新,我收到了这样的回复:

The document is never updated and I get this response:

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

我做错了什么?

推荐答案

所以 arrayFilters 选项和 位置过滤$[<identifier>] 实际上与自 MongoDB 3.5.12 以来的开发版本系列一起正常工作,并且在 MongoDB 3.6 系列的当前候选版本中,这实际上将正式发布.唯一的问题当然是驱动程序"在使用中实际上还没有赶上这一点.

So the arrayFilters option with positional filtered $[<identifier>] does actually work properly with the development release series since MongoDB 3.5.12 and also in the current release candidates for the MongoDB 3.6 series, where this will actually be officially released. The only problem is of course is that the "drivers" in use have not actually caught up to this yet.

重复我已经放在使用MongoDB更新嵌套数组的相同内容:

注意有点讽刺,因为这是在选项"中指定的..update() 和类似方法的参数,语法通常与所有最新发布的驱动程序版本兼容.

NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.

然而,mongo shell 并非如此,因为在那里实现方法的方式(具有讽刺意味的是向后兼容")arrayFilters 参数不被识别并由解析选项的内部方法移除以提供向后兼容性";使用先前的 MongoDB 服务器版本和遗留".update() API 调用语法.

However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.

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

So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.

所有这一切都意味着当前的驱动程序".update() 的实现实际上删除"了arrayFilters 定义的必要参数.对于 NodeJS,这将在驱动程序的 3.x 版本系列中解决,当然还有mongoose".然后在该版本发布后可能需要一些时间来实现它自己对更新驱动程序的依赖关系,然后将不再剥离"驱动程序.此类操作.

All this means is that the current "driver" implementation of .update() actually "removes" the necessary arguments with the definition of arrayFilters. For NodeJS this will be addressed in the 3.x release series of the driver, and of course "mongoose" will then likely take some time after that release to implement it's own dependencies on the updated driver, which would then no longer "strip" such actions.

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

You can however still run this on a supported server instance, by dropping back to the basic "update command" syntax usage, since this bypassed the implemented driver method:

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();
  }

})()

既然发送了命令"直接通过服务器,我们看到预期的更新确实发生了:

Since that sends the "command" directly through to the server, we see the expected update does in fact take place:

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
        }
      ]
    }
  ]
}

对了现在"[1]驱动程序可用现成"不实际实现 .update() 或者它的其他实现对应物以与实际传递必要的 arrayFilters 参数兼容的方式.所以如果你在玩"开发系列或发布候选服务器,那么您真的应该准备好使用前沿"以及未发布的驱动程序.

So right "now"[1] the drivers available "off the shelf" don't actually implement .update() or it's other implementing counterparts in a way that is compatible with actually passing through the necessary arrayFilters argument. So if you are "playing with" a development series or release candiate server, then you really should be prepared to be working with the "bleeding edge" and unreleased drivers as well.

但实际上您可以按照任何驱动程序中演示的方式执行此操作,以正确的形式执行,其中发出的命令不会被更改.

But you can actually do this as demonstrated in any driver, in the correct form where the command being issued is not going to be altered.

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

[1] As of writing on November 11th 2017 there is no "official" release of MongoDB or the supported drivers that actually implement this. Production usage should be based on official releases of the server and supported drivers only.

这篇关于使用 arrayFilters 更新 MongoDB 中的嵌套子文档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆