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

2023-12-12

我需要修改另一个数组内的数组内的文档。 我知道 MongoDB 不支持多个 '$' 同时迭代多个数组,但他们引入了数组过滤器为了那个原因。 看: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" : "[email protected]"
                }
            ]
        }
    ],
    "__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
}

我究竟做错了什么?


So the arrayFilters选项与位置过滤$[<identifier>]实际上,它确实可以与 MongoDB 3.5.12 以来的开发版本系列以及 MongoDB 3.6 系列的当前候选版本一起正常工作,该版本实际上将正式发布。当然,唯一的问题是正在使用的“驱动程序”实际上还没有赶上这一点。

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

NOTE有点讽刺的是,因为这是在“选项”参数中指定的.update()与方法一样,语法通常与所有最新发布的驱动程序版本兼容。

然而,事实并非如此mongoshell,因为该方法在那里实现的方式(“讽刺的是为了向后兼容”)arrayFilters解析选项的内部方法无法识别和删除参数,以便提供与先前 MongoDB 服务器版本和“遗留”的“向后兼容性”.update()API 调用语法。

所以如果你想在mongoshell 或其他“基于 shell”的产品(特别是 Robo 3T),您需要来自开发分支或生产版本的最新版本(3.6 或更高版本)。

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

但是,您仍然可以在支持的服务器实例,通过回退到基本“更新命令”语法用法,因为这绕过了实现的驱动程序方法:

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": "[email protected]"
            },
            {
              "_id": ObjectId("5a05a8dee0ce3444f8ec5bda"),
              "retries": "1",
              "priority": "1",
              "type": "email",
              "data": "[email protected]"
            }
          ]
        }
      ]
    });

    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: '[email protected]', _id: ObjectId("5a06557fb568aa0ad793c5e5") }, { _id: ObjectId("5a05a8dee0ce3444f8ec5bda"), retries: '1', priority: '1', type: 'email', data: '[email protected]' } ] } ], __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": "[email protected]",
          "_id": "5a06557fb568aa0ad793c5e5"
        },
        {
          "_id": "5a05a8dee0ce3444f8ec5bda",
          "retries": "1",
          "priority": "1",
          "type": "email",
          "data": "new data"            // <-- updated here
        }
      ]
    }
  ]
}

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.

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

[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.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐