आप मूल रूप से यहां जो चूक गए हैं वह उस क्षेत्र का "पथ" है जिसे आप populate()
करना चाहते हैं वास्तव में 'portfolio.formatType'
है और न केवल 'portfolio'
जैसा आपने टाइप किया है। हालांकि उस गलती और संरचना के कारण, आपको कुछ सामान्य गलतफहमियां हो सकती हैं।
सुधार को पॉप्युलेट करें
मूल सुधार के लिए केवल सही पथ की आवश्यकता होती है, और आपको model
. की आवश्यकता नहीं होती है तर्क क्योंकि यह पहले से ही स्कीमा में निहित है:
User.findById(req.params.id).populate('portfolio.formatType');
हालांकि आम तौर पर "एम्बेडेड" डेटा और "संदर्भित" डेटा दोनों को सरणी के भीतर "मिश्रण" करना एक अच्छा विचार नहीं है, और आपको वास्तव में या तो सबकुछ एम्बेड करना चाहिए या बस सबकुछ संदर्भित करना चाहिए। यदि आपका इरादा संदर्भित कर रहा है, तो दस्तावेज़ में संदर्भों की एक सरणी रखने के लिए सामान्य रूप से यह "एंटी-पैटर्न" का एक छोटा सा हिस्सा है, क्योंकि आपका कारण दस्तावेज़ को 16 एमबी बीएसओएन सीमा से आगे बढ़ने का कारण नहीं होना चाहिए। और जहां वह सीमा आपके डेटा तक कभी नहीं पहुंच पाएगी, वहां आमतौर पर "पूरी तरह से एम्बेड" करना बेहतर होता है। यह वास्तव में एक व्यापक चर्चा है, लेकिन आपको इसके बारे में पता होना चाहिए।
यहां अगला सामान्य बिंदु है populate()
अपने आप में कुछ हद तक "पुरानी टोपी" है, और वास्तव में "जादुई" चीज नहीं है, अधिकांश नए उपयोगकर्ता इसे मानते हैं। स्पष्ट होने के लिए populate()
शामिल नहीं है , और यह जो कुछ भी कर रहा है वह "संबंधित" आइटम वापस करने के लिए सर्वर पर एक और क्वेरी निष्पादित कर रहा है, फिर उस सामग्री को पिछली क्वेरी से लौटाए गए दस्तावेज़ों में मर्ज करें।
$लुकअप वैकल्पिक
यदि आप "जुड़ने" की तलाश में हैं, तो वास्तव में आप शायद पहले बताए गए "एम्बेडिंग" चाहते थे। यह वास्तव में "संबंधों" से निपटने का "मोंगोडीबी तरीका" है लेकिन सभी "संबंधित" डेटा को एक दस्तावेज़ में एक साथ रखना है। "जॉइन" का अन्य माध्यम जहां डेटा अलग-अलग संग्रह में है, $lookup
आधुनिक रिलीज में ऑपरेटर।
यह आपके "मिश्रित" सामग्री सरणी रूप के कारण थोड़ा अधिक जटिल हो जाता है, लेकिन इसे आम तौर पर इस प्रकार दर्शाया जा सकता है:
// Aggregation pipeline don't "autocast" from schema
const { Types: { ObjectId } } = require("mongoose");
User.aggregate([
{ "$match": { _id: ObjectId(req.params.id) } },
{ "$lookup": {
"from": FormatType.collection.name,
"localField": "portfolio.formatType",
"foreignField": "_id",
"as": "formats"
}},
{ "$project": {
"name": 1,
"portfolio": {
"$map": {
"input": "$portfolio",
"in": {
"name": "$$this.name",
"formatType": {
"$arrayElemAt": [
"$formats",
{ "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
]
}
}
}
}
}}
]);
या $lookup
के अधिक अभिव्यंजक रूप के साथ
MongoDB 3.6 के बाद से:
User.aggregate([
{ "$match": { _id: ObjectId(req.params.id) } },
{ "$lookup": {
"from": FormatType.collection.name,
"let": { "portfolio": "$portfolio" },
"as": "portfolio",
"pipeline": [
{ "$match": {
"$expr": {
"$in": [ "$_id", "$$portfolio.formatType" ]
}
}},
{ "$project": {
"_id": {
"$arrayElemAt": [
"$$portfolio._id",
{ "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
]
},
"name": {
"$arrayElemAt": [
"$$portfolio.name",
{ "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
]
},
"formatType": "$$ROOT",
}}
]
}}
]);
दोनों दृष्टिकोण थोड़ा अलग तरीके से काम करते हैं, लेकिन दोनों अनिवार्य रूप से मेल खाने वाली "संबंधित" प्रविष्टियों को वापस करने की अवधारणा के साथ काम करते हैं और फिर "name"
के साथ विलय करने के लिए मौजूदा सरणी सामग्री पर "री-मैपिंग" करते हैं। गुण "एम्बेडेड" सरणी के अंदर। यह वास्तव में मुख्य जटिलता है जो अन्यथा पुनर्प्राप्ति का एक बहुत ही सरल तरीका है।
यह काफी हद तक वैसी ही प्रक्रिया है जैसे populate()
वास्तव में "क्लाइंट" पर करता है लेकिन "सर्वर" पर निष्पादित होता है। इसलिए तुलनाएं $indexOfArray
का इस्तेमाल कर रही हैं
मिलान करने वाले ObjectId
. का पता लगाने के लिए ऑपरेटर मान हैं और फिर $arrayElemAt
ऑपरेशन।
केवल अंतर यह है कि MongoDB 3.6 संगत संस्करण में, हम "विदेशी" सामग्री के भीतर "प्रतिस्थापन" करते हैं "पहले" शामिल परिणाम माता-पिता को वापस कर दिए जाते हैं। पूर्व रिलीज़ में हम पूरी मेल खाने वाली विदेशी सरणी लौटाते हैं और फिर $map
।
हालांकि ये शुरू में "अधिक जटिल" लग सकते हैं, यहां बड़ा लाभ यह है कि ये एक "एकल अनुरोध" का गठन करते हैं। सर्वर पर "एकल प्रतिक्रिया" . के साथ और populate()
. के रूप में "एकाधिक" अनुरोध जारी और प्राप्त नहीं करना करता है। यह वास्तव में नेटवर्क ट्रैफ़िक में बहुत अधिक ओवरहेड बचाता है और प्रतिक्रिया समय को बहुत बढ़ाता है।
इसके अलावा, ये "असली जुड़ाव" हैं, इसलिए आप और भी बहुत कुछ कर सकते हैं जो "एकाधिक प्रश्नों" के साथ हासिल नहीं किया जा सकता है। उदाहरण के लिए आप "जॉइन" पर परिणामों को "क्रमबद्ध" कर सकते हैं और केवल शीर्ष परिणाम लौटा सकते हैं, जहां populate()
का उपयोग कर रहे हैं "सभी माता-पिता" को खींचने की जरूरत है इससे पहले कि वह यह भी देख सके कि कौन से "बच्चों" को परिणाम में वापस आना है। वही बच्चे के "शामिल होने" पर "फ़िल्टरिंग" शर्तों के लिए भी जाता है।
नेवला में आबाद होने के बाद क्वेरी करना सामान्य सीमाओं के बारे में और जहां आवश्यक हो ऐसे "जटिल" एकत्रीकरण पाइपलाइन बयानों की पीढ़ी को "स्वचालित" करने के लिए आप वास्तव में व्यावहारिक रूप से क्या कर सकते हैं।
प्रदर्शन
इन "जुड़ने" और सामान्य रूप से संदर्भित स्कीमा को समझने में एक और आम समस्या यह है कि लोगों को अक्सर संदर्भों को कहां और कब स्टोर करना है और यह सब कैसे काम करता है, इस पर अवधारणाएं गलत होती हैं। इसलिए निम्नलिखित सूचियाँ ऐसे डेटा के भंडारण और पुनर्प्राप्ति दोनों के प्रदर्शन के रूप में कार्य करती हैं।
पुराने NodeJS रिलीज़ के लिए मूल वादों के कार्यान्वयन में:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/usertest';
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const formatTypeSchema = new Schema({
name: String
});
const portfolioSchema = new Schema({
name: String,
formatType: { type: Schema.Types.ObjectId, ref: 'FormatType' }
});
const userSchema = new Schema({
name: String,
portfolio: [portfolioSchema]
});
const FormatType = mongoose.model('FormatType', formatTypeSchema);
const User = mongoose.model('User', userSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(function() {
mongoose.connect(uri).then(conn => {
let db = conn.connections[0].db;
return db.command({ buildInfo: 1 }).then(({ version }) => {
version = parseFloat(version.match(new RegExp(/(?:(?!-).)*/))[0]);
return Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()))
.then(() => FormatType.insertMany(
[ 'A', 'B', 'C' ].map(name => ({ name }))
)
.then(([A, B, C]) => User.insertMany(
[
{
name: 'User 1',
portfolio: [
{ name: 'Port A', formatType: A },
{ name: 'Port B', formatType: B }
]
},
{
name: 'User 2',
portfolio: [
{ name: 'Port C', formatType: C }
]
}
]
))
.then(() => User.find())
.then(users => log({ users }))
.then(() => User.findOne({ name: 'User 1' })
.populate('portfolio.formatType')
)
.then(user1 => log({ user1 }))
.then(() => User.aggregate([
{ "$match": { "name": "User 2" } },
{ "$lookup": {
"from": FormatType.collection.name,
"localField": "portfolio.formatType",
"foreignField": "_id",
"as": "formats"
}},
{ "$project": {
"name": 1,
"portfolio": {
"$map": {
"input": "$portfolio",
"in": {
"name": "$$this.name",
"formatType": {
"$arrayElemAt": [
"$formats",
{ "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
]
}
}
}
}
}}
]))
.then(user2 => log({ user2 }))
.then(() =>
( version >= 3.6 ) ?
User.aggregate([
{ "$lookup": {
"from": FormatType.collection.name,
"let": { "portfolio": "$portfolio" },
"as": "portfolio",
"pipeline": [
{ "$match": {
"$expr": {
"$in": [ "$_id", "$$portfolio.formatType" ]
}
}},
{ "$project": {
"_id": {
"$arrayElemAt": [
"$$portfolio._id",
{ "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
]
},
"name": {
"$arrayElemAt": [
"$$portfolio.name",
{ "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
]
},
"formatType": "$$ROOT",
}}
]
}}
]).then(users => log({ users })) : ''
);
})
.catch(e => console.error(e))
.then(() => mongoose.disconnect());
})()
और async/await
. के साथ वर्तमान एलटीएस v.8.x श्रृंखला सहित नए नोडजेएस रिलीज के लिए वाक्यविन्यास:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/usertest';
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const formatTypeSchema = new Schema({
name: String
});
const portfolioSchema = new Schema({
name: String,
formatType: { type: Schema.Types.ObjectId, ref: 'FormatType' }
});
const userSchema = new Schema({
name: String,
portfolio: [portfolioSchema]
});
const FormatType = mongoose.model('FormatType', formatTypeSchema);
const User = mongoose.model('User', userSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
let db = conn.connections[0].db;
let { version } = await db.command({ buildInfo: 1 });
version = parseFloat(version.match(new RegExp(/(?:(?!-).)*/))[0]);
log(version);
// Clean data
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Insert some things
let [ A, B, C ] = await FormatType.insertMany(
[ 'A', 'B', 'C' ].map(name => ({ name }))
);
await User.insertMany(
[
{
name: 'User 1',
portfolio: [
{ name: 'Port A', formatType: A },
{ name: 'Port B', formatType: B }
]
},
{
name: 'User 2',
portfolio: [
{ name: 'Port C', formatType: C }
]
}
]
);
// Show plain users
let users = await User.find();
log({ users });
// Get user with populate
let user1 = await User.findOne({ name: 'User 1' })
.populate('portfolio.formatType');
log({ user1 });
// Get user with $lookup
let user2 = await User.aggregate([
{ "$match": { "name": "User 2" } },
{ "$lookup": {
"from": FormatType.collection.name,
"localField": "portfolio.formatType",
"foreignField": "_id",
"as": "formats"
}},
{ "$project": {
"name": 1,
"portfolio": {
"$map": {
"input": "$portfolio",
"in": {
"name": "$$this.name",
"formatType": {
"$arrayElemAt": [
"$formats",
{ "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
]
}
}
}
}
}}
]);
log({ user2 });
// Expressive $lookup
if ( version >= 3.6 ) {
let users = await User.aggregate([
{ "$lookup": {
"from": FormatType.collection.name,
"let": { "portfolio": "$portfolio" },
"as": "portfolio",
"pipeline": [
{ "$match": {
"$expr": {
"$in": [ "$_id", "$$portfolio.formatType" ]
}
}},
{ "$project": {
"_id": {
"$arrayElemAt": [
"$$portfolio._id",
{ "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
]
},
"name": {
"$arrayElemAt": [
"$$portfolio.name",
{ "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
]
},
"formatType": "$$ROOT",
}}
]
}}
]);
log({ users })
}
mongoose.disconnect();
} catch(e) {
console.log(e)
} finally {
process.exit()
}
})()
बाद की सूची में यदि भागों की व्याख्या करने के लिए प्रत्येक चरण पर टिप्पणी की जाती है, और आप कम से कम तुलना करके देख सकते हैं कि वाक्य रचना के दोनों रूप एक दूसरे से कैसे संबंधित हैं।
ध्यान दें कि "अभिव्यंजक" $lookup
उदाहरण केवल वहीं चलता है जहां MongoDB सर्वर वास्तव में सिंटैक्स का समर्थन करता है।
और उन लोगों के लिए "आउटपुट" जिन्हें स्वयं कोड चलाने के लिए परेशान नहीं किया जा सकता:
Mongoose: formattypes.remove({}, {})
Mongoose: users.remove({}, {})
Mongoose: formattypes.insertMany([ { _id: 5b1601d8be9bf225554783f5, name: 'A', __v: 0 }, { _id: 5b1601d8be9bf225554783f6, name: 'B', __v: 0 }, { _id: 5b1601d8be9bf225554783f7, name: 'C', __v: 0 } ], {})
Mongoose: users.insertMany([ { _id: 5b1601d8be9bf225554783f8, name: 'User 1', portfolio: [ { _id: 5b1601d8be9bf225554783fa, name: 'Port A', formatType: 5b1601d8be9bf225554783f5 }, { _id: 5b1601d8be9bf225554783f9, name: 'Port B', formatType: 5b1601d8be9bf225554783f6 } ], __v: 0 }, { _id: 5b1601d8be9bf225554783fb, name: 'User 2', portfolio: [ { _id: 5b1601d8be9bf225554783fc, name: 'Port C', formatType: 5b1601d8be9bf225554783f7 } ], __v: 0 } ], {})
Mongoose: users.find({}, { fields: {} })
{
"users": [
{
"_id": "5b1601d8be9bf225554783f8",
"name": "User 1",
"portfolio": [
{
"_id": "5b1601d8be9bf225554783fa",
"name": "Port A",
"formatType": "5b1601d8be9bf225554783f5"
},
{
"_id": "5b1601d8be9bf225554783f9",
"name": "Port B",
"formatType": "5b1601d8be9bf225554783f6"
}
],
"__v": 0
},
{
"_id": "5b1601d8be9bf225554783fb",
"name": "User 2",
"portfolio": [
{
"_id": "5b1601d8be9bf225554783fc",
"name": "Port C",
"formatType": "5b1601d8be9bf225554783f7"
}
],
"__v": 0
}
]
}
Mongoose: users.findOne({ name: 'User 1' }, { fields: {} })
Mongoose: formattypes.find({ _id: { '$in': [ ObjectId("5b1601d8be9bf225554783f5"), ObjectId("5b1601d8be9bf225554783f6") ] } }, { fields: {} })
{
"user1": {
"_id": "5b1601d8be9bf225554783f8",
"name": "User 1",
"portfolio": [
{
"_id": "5b1601d8be9bf225554783fa",
"name": "Port A",
"formatType": {
"_id": "5b1601d8be9bf225554783f5",
"name": "A",
"__v": 0
}
},
{
"_id": "5b1601d8be9bf225554783f9",
"name": "Port B",
"formatType": {
"_id": "5b1601d8be9bf225554783f6",
"name": "B",
"__v": 0
}
}
],
"__v": 0
}
}
Mongoose: users.aggregate([ { '$match': { name: 'User 2' } }, { '$lookup': { from: 'formattypes', localField: 'portfolio.formatType', foreignField: '_id', as: 'formats' } }, { '$project': { name: 1, portfolio: { '$map': { input: '$portfolio', in: { name: '$$this.name', formatType: { '$arrayElemAt': [ '$formats', { '$indexOfArray': [ '$formats._id', '$$this.formatType' ] } ] } } } } } } ], {})
{
"user2": [
{
"_id": "5b1601d8be9bf225554783fb",
"name": "User 2",
"portfolio": [
{
"name": "Port C",
"formatType": {
"_id": "5b1601d8be9bf225554783f7",
"name": "C",
"__v": 0
}
}
]
}
]
}
Mongoose: users.aggregate([ { '$lookup': { from: 'formattypes', let: { portfolio: '$portfolio' }, as: 'portfolio', pipeline: [ { '$match': { '$expr': { '$in': [ '$_id', '$$portfolio.formatType' ] } } }, { '$project': { _id: { '$arrayElemAt': [ '$$portfolio._id', { '$indexOfArray': [ '$$portfolio.formatType', '$_id' ] } ] }, name: { '$arrayElemAt': [ '$$portfolio.name', { '$indexOfArray': [ '$$portfolio.formatType', '$_id' ] } ] }, formatType: '$$ROOT' } } ] } } ], {})
{
"users": [
{
"_id": "5b1601d8be9bf225554783f8",
"name": "User 1",
"portfolio": [
{
"_id": "5b1601d8be9bf225554783fa",
"name": "Port A",
"formatType": {
"_id": "5b1601d8be9bf225554783f5",
"name": "A",
"__v": 0
}
},
{
"_id": "5b1601d8be9bf225554783f9",
"name": "Port B",
"formatType": {
"_id": "5b1601d8be9bf225554783f6",
"name": "B",
"__v": 0
}
}
],
"__v": 0
},
{
"_id": "5b1601d8be9bf225554783fb",
"name": "User 2",
"portfolio": [
{
"_id": "5b1601d8be9bf225554783fc",
"name": "Port C",
"formatType": {
"_id": "5b1601d8be9bf225554783f7",
"name": "C",
"__v": 0
}
}
],
"__v": 0
}
]
}