Mongoose Nested Array $ push

时间:2018-05-21 01:48:26

标签: mongodb mongoose mongodb-query

我正在尝试$push Object进入嵌套数组,但它似乎无法正常工作。我不确定我做错了什么。

我的数据库是这样的:

{
  customers: {
    name: String,
    address: String,
    proj_managers: [
      {
        name: String,
        username: String,
        projects: [
          name: String,
          tags: [
            {
              tag_no: Number,
              tag_id: String,
              time: String,
              product_id: String,
              urls: [
                url: String,
                count: Number
              ],
              gps: String,
              deactivate: Boolean
            }
          ]
        ]
      }
    ]
  }
}

所以我要做的是$push tags的一系列标记,用于特定的project。我的后端使用GraphQL

/index.js

// GraphQL schema
import schema from './schema'
// Mongoose Models
import Customer from './models/Customer'
import Manager from './models/Manager'
// Access to GraphQL API
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema, context: { Customer, Manager } }))
app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }))

/schema/index.js

import { bundle } from 'graphql-modules'
import { makeExecutableSchema } from 'graphql-tools'

// GraphQL Modules
import Manager from './Manager'
import Customer from './Customer'
import ProjectManager from './ProjectManager'
import Project from './Project'
import Tag from './Tag'
import URL from './URL'

const modules = [Manager, Customer, ProjectManager, Project, Tag, URL]

export default makeExecutableSchema(bundle(modules))

/schema/Project.js

const schema = `
  type Project {
    _id: ID,
    name: String!,
    description: String,
    tags: [Tag],
    deactivate: Boolean!
  }
  input TagInput {
    tagNo: Int!,
    tagId: String!,
    time: String,
    productId: String,
    url1: String,
    url2: String,
    gps: String
  }
`

const queries = `
  projects(customerUsername: String!): [Project],
  project(projectID: ID!): Project
`

const mutations = `
  editProject(id: ID!, name: String, description: String, deactivate: Boolean, customerUsername: String!, pmUsername: String!): String,
  addProject(name: String!, description: String, customerID: ID!, pmUsername: String!): String,
  pushTags(customerID: String!, username: String!, projectID: ID!, isManager: Boolean!, tags: [TagInput]!): String
`

const pushTags = async (root, { tags, customerID, username, projectID, isManager }, { Customer }) => {
  let result = ''
  let query = { _id: customerID }
  let update = {}
  let ts = []
  let options = {
    arrayFilters: [
      { 'a.username': username },
      { 'b._id': projectID }
    ]
  }
  tags.forEach(tag => {
    if (isManager) {
      ts.push({
        tag_no: tag.tagNo,
        tag_id: tag.tagId,
        time: new Date(),
        product_id: tag.productId,
        urls: [
          { url: tag.url1, count: 0 },
          { url: tag.url2, count: 0 }
        ],
        gps: tag.gps,
        deactivate: false
      })
    } else {
      update = {
        $set: {
          'proj_managers.$[a].projects.$[b].tags': {
            product_id: tag.productId,
            urls: [
              { url: tag.url1 },
              { url: tag.url2 }
            ],
            gps: tag.gps
          }
        }
      }
    }
  })
  if (isManager) {
    update = {
      $push: {
        'proj_managers.$[a].projects.$[b].tags': {
          $each: ts
        }
      }
    }
    result = await Customer.update(query, update, options)
  }
  return result.ok && result.nModified ? 'Success' : 'Failed'
}

const resolvers = {
  queries: {
    projects,
    project
  },
  mutations: {
    addProject,
    editProject,
    pushTags
  }
}

export default () => ({
  schema,
  queries,
  mutations,
  resolvers
})

发送到pushTags突变的标记是:

[
  {
    "tagNo":"1",
    "tagId":"02F9AMCGA38O7L",
    "productId":"",
    "url1":"",
    "url2":"",
    "gps":""
  },{
    "tagNo":"2",
    "tagId":"028MFL6EV5L904",
    "productId":"",
    "url1":"",
    "url2":"",
    "gps":""
  },{
    "tagNo":"3",
    "tagId":"02XDWCIL6W2IIX",
    "productId":"",
    "url1":"",
    "url2":"",
    "gps":""
  }
];

文件

{
  "_id": ObjectId("5b0216f1cf14851f18e4312b"),
  "deactivate": false,
  "name": "Razer",
  "address": "201 3rd Street, Suite 900 San Francisco, CA 94103 USA",
  "phone_no": "0987654321",
  "proj_managers": [
    {
      "deactivate": false,
      "_id": ObjectId("5b021750cf14851f18e4312c"),
      "name": "Liang-Shih Lin",
      "username": "troopy",
      "phone_no": "0987654321",
      "password": "$2b$10$eOVoRkfmkHQyHkc6XaDanunUuyi0EFy.oZ.dRgKJYxBciMLYUVy0W",
      "projects": [
        {
          "deactivate": false,
          "_id": ObjectId("5b0217d4cf14851f18e4312d"),
          "name": "Razer Godzilla",
          "description": "A Godzilla Mouse",
          "tags": [ ]
        }
      ]
    }
  ],
  "__v": 0
}

我尝试使用findByIdAndUpdateupdateOne,使用forEach()辅助函数循环遍历标记并将$push逐个放入数据库中,但似乎没有工作。我认为这可能是我的arrayFilters,我将b._id更改为b.name,但这也无效。

我尝试在mongo shell中使用此查询:

db.customers.update({ _id: "5afe642ed42aee261cb3292e" }, { $push: { "proj_managers.$[a].projects.$[b].tags": { tag_no: 1, tag_id: "0476F06A594980", time: "2018-05-20T23:18:18.824Z", product_id: "xr235Yf4", urls: [{url: "example.com", count: 0}, {url: "example2.com", count: 0}], gps: "", deactivate: false} } }, { arrayFilters: [{ "a.username": "joliver" }, { "b.id": "5b01367b6d053860e90e0f9f" }] })

结果:

WriteResult({
  "nMatched": 0,
  "nUpserted": 0,
  "nModified": 0
})

如果你想看看整个项目,这里是link

1 个答案:

答案 0 :(得分:1)

您在尝试中遗漏的是arrayFilters条目没有" autocast"像mongoose操作中的其他属性一样,基于" schema"具有。这是因为没有任何东西实际上将条件与定义的模式中的特定细节联系起来,或者至少就当前的mongoose发布处理它而言。

因此,如果您与_id内的arrayFilters匹配,则需要实际"施放" ObjectId值自己来源于"字符串":

let updated = await Customer.findOneAndUpdate(
  {
    "_id": "5b0216f1cf14851f18e4312b",              //<-- mongoose can autocast these
    "proj_managers": {
      "$elemMatch": {
        "username": "troopy",
        "projects._id": "5b0217d4cf14851f18e4312d" //<-- here as well
      }
    }
  },
  {
    "$push": {
      "proj_managers.$[a].projects.$[b].tags": { "$each": tags }
    }
  },
  {
    "new": true,
    // But not in here
    "arrayFilters": [
      { "a.username": "troopy" },
      { "b._id": ObjectId("5b0217d4cf14851f18e4312d") }  // <-- Cast manually
    ]
  }
);

然后你得到你应该得到的结果。为了演示而减少一点:

{
  "_id": "5b0216f1cf14851f18e4312b",
  "name": "Bill",
  "address": "1 some street",
  "proj_managers": [
    {
      "projects": [
        {
          "tags": [
            {
              "_id": "5b0239cc0a7a34219b0efdab",
              "tagNo": 1,
              "tagId": "02F9AMCGA38O7L",
              "productId": "",
              "url1": "",
              "url2": "",
              "gps": ""
            },
            {
              "_id": "5b0239cc0a7a34219b0efdaa",
              "tagNo": 2,
              "tagId": "028MFL6EV5L904",
              "productId": "",
              "url1": "",
              "url2": "",
              "gps": ""
            },
            {
              "_id": "5b0239cc0a7a34219b0efda9",
              "tagNo": 3,
              "tagId": "02XDWCIL6W2IIX",
              "productId": "",
              "url1": "",
              "url2": "",
              "gps": ""
            }
          ],
          "_id": "5b0217d4cf14851f18e4312d",
          "name": "Razer Godzilla"
        }
      ],
      "_id": "5b021750cf14851f18e4312c",
      "name": "Ted",
      "username": "troopy"
    }
  ],
  "__v": 0
}

所以这里的主要内容是从ObjectId导入Types.ObjectId方法并实际投射任何字符串。来自外部请求的输入通常是&#34;字符串&#34;。

所以现在,只要你想要将这些值与positional filtered $[<identifier>]运算符和arrayFilters的匹配结合起来,就记住实际上&#34;转换类型&#34;。

请注意,在此处使用$elemMatch表示阵列上的相同匹配条件实际上并不是&#34;要求&#34;但可能应该始终被视为最佳做法。原因是虽然arrayFilters条件实际上将决定实际改变的内容的选择,但用&#34;查询&#34;确保数组上存在相同条件的条件只是确保文档永远不会被考虑,这实际上减少了处理开销。

另请注意,因为您在mongoose架构固有的每个阵列成员中使用唯一的_id值,所以您基本上可以&#34;逃避&#34; :< / p>

let wider = await Customer.findOneAndUpdate(
  { "_id": "5b0216f1cf14851f18e4312b" },
  { "$push": {
    "proj_managers.$[].projects.$[b].tags": { "$each": extra }
  }},
  {
    "new": true,
    "arrayFilters": [
      { "b._id": ObjectId("5b0217d4cf14851f18e4312d") }
    ]
  }
);

所以实际上使用positional all $[]而不是如上所述,只是跳过其他条件而转向&#34; unique&#34; ObjectId值。它看起来更轻,但它实际上通过不必要地检查可能的各种数组路径来增加一些cpu周期,更不用说如果数组不满足其他条件则文档本身匹配。

如果没有&#34;警告&#34; ,即使现代MongoDB版本支持这一点,我也无法离开这一点,但它仍然是不可取的。有嵌套数组。是的,可以使用现代功能更新它们,但是查询&#34;它仍然要复杂得多。它们比根据需要在单独的集合中使用更平坦的数组结构甚至完全展平的数据。

Updating a Nested Array with MongoDBFind in Double Nested Array MongoDB处有更多描述,但在大多数情况下,感知&#34;组织&#34;结构为&#34;嵌套&#34;实际上并不存在,而且它更像是一种障碍。

以及演示工作更新的完整列表:

const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/test';

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

const tagSchema = new Schema({
  tagNo: Number,
  tagId: String,
  productId: String,
  url1: String,
  url2: String,
  gps: String
})

const projectSchema = new Schema({
  name: String,
  tags: [tagSchema]
})

const projManagerSchema = new Schema({
  name: String,
  username: String,
  projects: [projectSchema]
});

const customerSchema = new Schema({
  name: String,
  address: String,
  proj_managers: [projManagerSchema]
});


const Customer = mongoose.model('Customer', customerSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

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

    await Customer.create({
      _id: "5b0216f1cf14851f18e4312b",
      name: 'Bill',
      address: '1 some street',
      proj_managers: [
        {
          _id: "5b021750cf14851f18e4312c",
          name: "Ted",
          username: "troopy",
          projects: [
            {
              _id: "5b0217d4cf14851f18e4312d",
              name: "Razer Godzilla",
              tags: [  ]
            }
          ]
        }
      ]
    });

    const tags = [
      {
        "tagNo":"1",
        "tagId":"02F9AMCGA38O7L",
        "productId":"",
        "url1":"",
        "url2":"",
        "gps":""
      },{
        "tagNo":"2",
        "tagId":"028MFL6EV5L904",
        "productId":"",
        "url1":"",
        "url2":"",
        "gps":""
      },{
        "tagNo":"3",
        "tagId":"02XDWCIL6W2IIX",
        "productId":"",
        "url1":"",
        "url2":"",
        "gps":""
      }
    ];

    const extra = [{
      "tagNo":"4",
      "tagId":"02YIVGMFZBC9OI",
      "productId":"",
      "url1":"",
      "url2":"",
      "gps":""
    }];

    let cust = await Customer.findOne({
      "_id": "5b0216f1cf14851f18e4312b",
      "proj_managers": {
        "$elemMatch": {
          "username": "troopy",
          "projects._id": "5b0217d4cf14851f18e4312d"
        }
      }
    });
    log(cust);
    let updated = await Customer.findOneAndUpdate(
      {
        "_id": "5b0216f1cf14851f18e4312b",
        "proj_managers": {
          "$elemMatch": {
            "username": "troopy",
            "projects._id": "5b0217d4cf14851f18e4312d"
          }
        }
      },
      {
        "$push": {
          "proj_managers.$[a].projects.$[b].tags": { "$each": tags }
        }
      },
      {
        "new": true,
        "arrayFilters": [
          { "a.username": "troopy" },
          { "b._id": ObjectId("5b0217d4cf14851f18e4312d") }
        ]
      }
    );
    log(updated);

    let wider = await Customer.findOneAndUpdate(
      { "_id": "5b0216f1cf14851f18e4312b" },
      { "$push": {
        "proj_managers.$[].projects.$[b].tags": { "$each": extra }
      }},
      {
        "new": true,
        "arrayFilters": [
          { "b._id": ObjectId("5b0217d4cf14851f18e4312d") }
        ]
      }
    );
    log(wider);

    mongoose.disconnect();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()