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

查看:826
本文介绍了使用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}]}]}

以下是设置文档的方式:

Here's how the documents are set:

{
    "_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选项带有

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版本系列中得到解决,当然,猫鼬"可能会在该版本之后花费一些时间来实现其对更新驱动程序的依赖,从而不再需要"strip"这样的动作.

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.

不过,您仍然可以通过返回到基本的语法用法,因为它绕过了已实现的驱动程序方法:

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天全站免登陆