Lia*_*Lin 4 mongoose mongodb 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
}
]
]
}
]
}
}
Run Code Online (Sandbox Code Playgroud)
所以我想做的是将$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' }))
Run Code Online (Sandbox Code Playgroud)
/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))
Run Code Online (Sandbox Code Playgroud)
/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
})
Run Code Online (Sandbox Code Playgroud)
发送到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":""
}
];
Run Code Online (Sandbox Code Playgroud)
文件
{
"_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
}
Run Code Online (Sandbox Code Playgroud)
我尝试过使用findByIdAndUpdate, updateOne, 使用forEach()辅助函数来循环遍历标签并将$push其一一放入数据库中,但似乎没有任何效果。我以为这可能是我的arrayFilters,我改为b._id,b.name但这也不起作用。
我尝试在mongoshell 中使用此查询:
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" }] })
Run Code Online (Sandbox Code Playgroud)
结果:
WriteResult({
"nMatched": 0,
"nUpserted": 0,
"nModified": 0
})
Run Code Online (Sandbox Code Playgroud)
如果您想查看整个项目,请点击以下链接
您在尝试中错过的是,arrayFilters条目不会像猫鼬操作中的其他属性一样根据“模式”的值进行“自动转换”。这是因为没有任何东西实际上将条件与定义的模式中的特定细节联系起来,或者至少就当前的 mongoose 版本处理它而言是这样。
因此,如果您匹配_idinside 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
]
}
);
Run Code Online (Sandbox Code Playgroud)
然后你就得到了你应该得到的结果。为了演示而将其缩小一点:
{
"_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
}
Run Code Online (Sandbox Code Playgroud)
因此,这里的主要任务是导入ObjectId方法Types.ObjectId并实际转换您拥有的任何字符串。来自外部请求的输入通常是“字符串”。
因此,现在,每当您想要将这些值与位置过滤$[<identifier>]运算符的匹配相结合时,arrayFilters只需记住实际“转换类型”即可。
请注意,$elemMatch此处对数组使用相同的匹配标准实际上并不是“要求”,但可能始终应被视为最佳实践。原因是,虽然arrayFilters条件实际上决定了实际更改内容的选择,但用“查询”条件支持它以确保数组上确实存在相同的条件只是确保文档永远不会被考虑,而这实际上减少处理开销。
_id另请注意,因为您在猫鼬模式固有的每个数组成员中使用唯一值,所以您基本上可以“逃脱”:
let wider = await Customer.findOneAndUpdate(
{ "_id": "5b0216f1cf14851f18e4312b" },
{ "$push": {
"proj_managers.$[].projects.$[b].tags": { "$each": extra }
}},
{
"new": true,
"arrayFilters": [
{ "b._id": ObjectId("5b0217d4cf14851f18e4312d") }
]
}
);
Run Code Online (Sandbox Code Playgroud)
因此,实际上使用位置 all$[]来代替,并且如上所述,只是跳过其他条件以支持“唯一”ObjectId值。它看起来更轻,但实际上它通过不必要地检查潜在的各种数组路径而增加了一些 cpu 周期,更不用说如果数组根本不满足其他条件,则文档本身的匹配。
我也不能真正离开这一点而没有“警告”,即使现代 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()
}
})()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3797 次 |
| 最近记录: |