Что вы упустили в своих попытках, так это то, что записи 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()
}
})()