Mongoose Nested Array $ push - PullRequest
0 голосов
/ 21 мая 2018

Я пытаюсь $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
}

Я пытался использовать findByIdAndUpdate, updateOne, использовать вспомогательную функцию forEach() для циклического перемещения по тегам и $push в базу данных один за другим, нокажется, ничего не работает.Я думал, что это может быть мой arrayFilters, я изменил b._id на b.name, но это тоже не сработало.

Я попытался использовать это в оболочке mongo с этим запросом:

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

Если вы хотите взглянуть на весь проект, вот ссылка

1 Ответ

0 голосов
/ 21 мая 2018

Что вы упустили в своих попытках, так это то, что записи arrayFilters не "автоматически транслируются", как другие свойства в операциях mongoose, в зависимости от того, какое значение имеет "схема".Это связано с тем, что там нет ничего, что фактически связывало бы условие с конкретной деталью в определенной схеме или, по крайней мере, в той степени, в которой его обрабатывает текущий выпуск 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 и фактически приводить любые строки, которые у вас есть.Входные данные из внешних запросов, как правило, являются «строками».

Так что сейчас, когда вы хотите, чтобы такие значения в сочетании с соответствиями для оператора позиционной фильтрации $[<identifier>] и arrayFilters, просто не забывайтена самом деле "типы приведения".

Обратите внимание, что использование $elemMatch здесь для одинаковых критериев соответствия в массиве на самом деле не является "требованием", но, вероятно, всегда должно считаться наилучшей практикой.Причина в том, что в то время как условия arrayFilters будут фактически определять выбор того, что действительно будет изменено, подкрепляя это условием «запроса», чтобы гарантировать, что те же условия существуют в массиве, просто гарантирует, что документ никогда даже не будет рассмотрен,и это фактически снижает накладные расходы на обработку.

Также обратите внимание, что поскольку вы используете уникальное значение _id в каждом элементе массива, свойственном схеме mongoose, то вы можете "покончить с" :

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

Таким образом, вместо этого используется позиционное все $[] и, как уже упоминалось, просто пропускаются другие условия в пользу «уникальных» значений ObjectId.Он кажется легче, но на самом деле он добавляет несколько циклов процессора, излишне проверяя потенциально различные пути массива, не говоря уже о совпадении в самом документе, если массив просто не удовлетворял другим условиям.

Я также могу 'на самом деле это не нужно оставлять без "предостережения" , где хотя современные выпуски MongoDB будут поддерживать это, по-прежнему не рекомендуется иметь вложенные массивы.Да, их можно обновить современными функциями, но все же гораздо сложнее их «запросить», чем использование гораздо более плоской структуры массива или даже полностью сплющенных данных в отдельной коллекции, в зависимости от потребностей.

Естьподробное описание на Обновление вложенного массива с помощью MongoDB и Поиск в двойном вложенном массиве MongoDB , но в большинстве случаев действительно верно, что воспринимаемая "организация" структурирования как "вложенная" на самом делене существует, и это действительно больше помеха.

И полный список, чтобы продемонстрировать рабочее обновление:

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

})()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...