我正在尝试使用Mongoose的Model.populate()将用户ID转换为用户,在通过聚合和展开获取的文档的子结构中。我猜测我的架构有问题或者可能解开是打破了与子架构的连接。
问题是:当存在有效引用时,不会调用填充回调。如果没有子结构,则调用回调,原始文档不变。
结构:
我有文章,用户可以分别没有或多个ArticleRatings。
我在ArticleRating中使用了两个引用,与文章和评分用户相关。
流程:
该过程实际上是将文章导出为(旧)CSV格式并将结构展平为具有用户特定评级的重复文章行。展开非常适合此操作,并且保留空值可以保留没有评级的文章。
调试:
我已经尝试深入了解Model.populate代码。所有的promises和回调包装器都变得非常复杂,但我可以看到底层的填充调用也没有调用内部回调。我没有使用承诺变种 - 但不是100%肯定我是否应该? (Mongoose docs对回调和承诺之间的用例有点模糊)。
我已经仔细检查了我的架构,尝试将模型显式添加到填充调用中(因为它不需要在架构中)。没有错误或例外,它不会崩溃。
逐步浏览Chrome调试器中的代码就像我期望的那样显示模型:前几篇文章的rating.userId具有有效的ObjectId,但在这种情况下,填充回调根本就没有被叫。接下来的一百篇文章没有"等级" set,并且可以为所有这些调用可靠地调用回调。
所以我猜测我做错了什么导致Model.populate走上一条没有正确错误的路径?
注意:我知道我可以重写代码以使用聚合$ lookup或其他嵌入结构而不是外部引用,但我是在功能拼图的最后一部分,并希望将其作为 - 是
这是简化的架构:
const ArticleRatingSchema = new Schema({
articleId: {type: Schema.Types.ObjectId, ref:'Article'},
userId: {type: Schema.Types.ObjectId, ref:'User'},
rating: String,
comment: String,
});
const ArticleSchema = new Schema({
title: String,
rating: ArticleRatingSchema,
});
这是查找
// Find all articles relating to this project, and their ratings.
// Unwind does the duplicate per-user, and preserve keeps un-rated articles.
articleModel.aggregate([
{$match: {projectId: projectId}},
{$lookup:{from:'articleratings', localField:'_id', foreignField:'articleId', as:'rating' }},
{$unwind: {path:'$rating', preserveNullAndEmptyArrays:true}}
], (err, models) =>
{
if (!err) {
models.map((article) => {
articleModel.populate(article, {path:'rating.userId', model:'User'}, (err, article)=> {
// Process the article...
// this callback only gets called where there is NO rating in the article.
});
});
}
答案 0 :(得分:0)
我意识到这是因为我在同步map()循环中处理集合,并且两种情况必须不同,因为不匹配的populate是同步回调,而匹配的替换稍后回调。
如果我在回调中使用console.log(),我发现在已经格式化和下载CSV之后,最后处理了四个匹配的案例。
所以答案是:填充IS被调用,但异步。
我需要重新设计map()循环以适应通常的异步模式。
答案 1 :(得分:0)
我个人对您认为在聚合管道中使用$lookup
然后想要.populate()
结果感到困惑。因为要求使用.populate()
实质上意味着正在向服务器发出其他查询,以便模拟联接"。
因此,$lookup
实际上是"加入服务器"那么你真的应该只使用$lookup
。
您可以使用.populate()
,我会显示一些代码,以表明可以完成。但这里真的很多余,因为你可以在服务器上完成所有的工作。
所以我最好的"近似"您似乎拥有的结构是:
<强>制品强>
{
"_id" : ObjectId("5962104312246235cdcceb16"),
"title" : "New News",
"ratings" : [ ],
"__v" : 0
}
<强> articleratings 强>
{
"_id" : ObjectId("5962104312246235cdcceb17"),
"articleId" : ObjectId("5962104312246235cdcceb16"),
"userId" : ObjectId("5962104312246235cdcceb13"),
"rating" : "5",
"comment" : "Great!",
"__v" : 0
}
{
"_id" : ObjectId("5962104312246235cdcceb18"),
"articleId" : ObjectId("5962104312246235cdcceb16"),
"userId" : ObjectId("5962104312246235cdcceb14"),
"rating" : "3",
"comment" : "Okay I guess ;)",
"__v" : 0
}
{
"_id" : ObjectId("5962104312246235cdcceb19"),
"articleId" : ObjectId("5962104312246235cdcceb16"),
"userId" : ObjectId("5962104312246235cdcceb15"),
"rating" : "1",
"comment" : "Hated it :<",
"__v" : 0
}
用户强>
{
"_id" : ObjectId("5962104312246235cdcceb13"),
"name" : "Bill",
"email" : "bill@example.com",
"__v" : 0
}
{
"_id" : ObjectId("5962104312246235cdcceb14"),
"name" : "Fred",
"email" : "fred@example.com",
"__v" : 0
}
{
"_id" : ObjectId("5962104312246235cdcceb15"),
"name" : "Ted",
"email" : "ted@example.com",
"__v" : 0
}
然后是汇总声明:
Article.aggregate(
[
{ "$lookup": {
"from": ArticleRating.collection.name,
"localField": "_id",
"foreignField": "articleId",
"as": "ratings"
}},
{ "$unwind": "$ratings" },
{ "$lookup": {
"from": User.collection.name,
"localField": "ratings.userId",
"foreignField": "_id",
"as": "ratings.userId",
}},
{ "$unwind": "$ratings.userId" },
{ "$group": {
"_id": "$_id",
"title": { "$first": "$title" },
"ratings": { "$push": "$ratings" }
}}
],
(err,articles) => {
if (err) callback(err);
log(articles);
callback();
}
)
结果:
{
"_id": "5962126f3ef2fb35efeefd94",
"title": "New News",
"ratings": [
{
"_id": "5962126f3ef2fb35efeefd95",
"articleId": "5962126f3ef2fb35efeefd94",
"userId": {
"_id": "5962126f3ef2fb35efeefd91",
"name": "Bill",
"email": "bill@example.com",
"__v": 0
},
"rating": "5",
"comment": "Great!",
"__v": 0
},
{
"_id": "5962126f3ef2fb35efeefd96",
"articleId": "5962126f3ef2fb35efeefd94",
"userId": {
"_id": "5962126f3ef2fb35efeefd92",
"name": "Fred",
"email": "fred@example.com",
"__v": 0
},
"rating": "3",
"comment": "Okay I guess ;)",
"__v": 0
},
{
"_id": "5962126f3ef2fb35efeefd97",
"articleId": "5962126f3ef2fb35efeefd94",
"userId": {
"_id": "5962126f3ef2fb35efeefd93",
"name": "Ted",
"email": "ted@example.com",
"__v": 0
},
"rating": "1",
"comment": "Hated it :<",
"__v": 0
}
]
}
对于&#34;填充&#34;没有意义的地方引用&#34; articleId&#34;关于&#34;评级&#34;他们自己。但我们确实已经填充了#34; &#34;评级&#34;到文章和&#34;用户&#34;对于每个评级。
使用.populate()
(在$lookup
之后)和您正在尝试的方式同时显示两种方式,并且只使用普通$lookup
。
方法同时使用&#34;普通承诺&#34;并与async.map
交替使用:
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
mongoose.connect('mongodb://localhost/publication');
const userSchema = new Schema({
name: String,
email: String
});
const User = mongoose.model('User', userSchema);
const articleRatingSchema = new Schema({
articleId: {type: Schema.Types.ObjectId, ref:'Article'},
userId: {type: Schema.Types.ObjectId, ref:'User'},
rating: String,
comment: String,
});
const articleSchema = new Schema({
title: String,
ratings: [articleRatingSchema]
})
const Article = mongoose.model('Article', articleSchema);
const ArticleRating = mongoose.model('ArticleRating', articleRatingSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
const userData = [
{ name: 'Bill', rating: 5, comment: 'Great!' },
{ name: 'Fred', rating: 3, comment: 'Okay I guess ;)' },
{ name: 'Ted', rating: 1, comment: 'Hated it :<' }
];
async.series(
[
// Clean data
(callback) =>
async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Insert data
(callback) =>
async.waterfall(
[
// User and article
(callback) =>
async.parallel(
{
"users": (callback) =>
User.create(
["Bill", "Fred", "Ted"].map( name =>
({ name, email: `${name.toLowerCase()}@example.com` })
),
callback
),
"article": (callback) =>
Article.create({ title: "New News" },callback)
},
callback
),
// Article Ratings
(data,callback) =>
ArticleRating.create(
data.users.map( u => ({
articleId: data.article._id,
userId: u._id,
rating: userData.find( ud => ud.name === u.name ).rating,
comment: userData.find( ud => ud.name === u.name ).comment
})),
callback
)
],
callback
),
// $lookup and populate async.map
(callback) =>
Article.aggregate(
[
{ "$lookup": {
"from": ArticleRating.collection.name,
"localField": "_id",
"foreignField": "articleId",
"as": "ratings"
}}
],
(err,articles) => {
if (err) callback(err);
async.map(
articles.map( a => new Article(a) ),
(article,callback) =>
async.map(
article.ratings,
(rating,callback) =>
ArticleRating.populate(rating,{ path: 'userId' },callback),
(err,ratings) => {
if (err) callback(err);
article.ratings = ratings
callback(null,article)
}
),
(err,articles) => {
if (err) callback(err);
log(articles);
callback();
}
)
}
),
// $look and populate Promise
(callback) =>
Article.aggregate(
[
{ "$lookup": {
"from": ArticleRating.collection.name,
"localField": "_id",
"foreignField": "articleId",
"as": "ratings"
}}
]
)
.then(articles =>
Promise.all(
articles.map( a => new Article(a) ).map(article =>
new Promise((resolve,reject) => {
Promise.all(
article.ratings.map( rating =>
ArticleRating.populate(rating,{ path: 'userId' })
)
).then(ratings => {
article.ratings = ratings;
resolve(article);
}).catch(reject)
})
)
)
)
.then(articles => {
log(articles);
callback();
})
.catch(err => callback(err)),
// Plain $lookup
(callback) =>
Article.aggregate(
[
{ "$lookup": {
"from": ArticleRating.collection.name,
"localField": "_id",
"foreignField": "articleId",
"as": "ratings"
}},
{ "$unwind": "$ratings" },
{ "$lookup": {
"from": User.collection.name,
"localField": "ratings.userId",
"foreignField": "_id",
"as": "ratings.userId",
}},
{ "$unwind": "$ratings.userId" },
{ "$group": {
"_id": "$_id",
"title": { "$first": "$title" },
"ratings": { "$push": "$ratings" }
}}
],
(err,articles) => {
if (err) callback(err);
log(articles);
callback();
}
)
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
);
完整输出
Mongoose: users.remove({}, {})
Mongoose: articles.remove({}, {})
Mongoose: articleratings.remove({}, {})
Mongoose: users.insert({ name: 'Bill', email: 'bill@example.com', _id: ObjectId("596219ff6f73ed36d868ed40"), __v: 0 })
Mongoose: users.insert({ name: 'Fred', email: 'fred@example.com', _id: ObjectId("596219ff6f73ed36d868ed41"), __v: 0 })
Mongoose: users.insert({ name: 'Ted', email: 'ted@example.com', _id: ObjectId("596219ff6f73ed36d868ed42"), __v: 0 })
Mongoose: articles.insert({ title: 'New News', _id: ObjectId("596219ff6f73ed36d868ed43"), ratings: [], __v: 0 })
Mongoose: articleratings.insert({ articleId: ObjectId("596219ff6f73ed36d868ed43"), userId: ObjectId("596219ff6f73ed36d868ed40"), rating: '5', comment: 'Great!', _id: ObjectId("596219ff6f73ed36d868ed44"), __v: 0 })
Mongoose: articleratings.insert({ articleId: ObjectId("596219ff6f73ed36d868ed43"), userId: ObjectId("596219ff6f73ed36d868ed41"), rating: '3', comment: 'Okay I guess ;)', _id: ObjectId("596219ff6f73ed36d868ed45"), __v: 0 })
Mongoose: articleratings.insert({ articleId: ObjectId("596219ff6f73ed36d868ed43"), userId: ObjectId("596219ff6f73ed36d868ed42"), rating: '1', comment: 'Hated it :<', _id: ObjectId("596219ff6f73ed36d868ed46"), __v: 0 })
Mongoose: articles.aggregate([ { '$lookup': { from: 'articleratings', localField: '_id', foreignField: 'articleId', as: 'ratings' } } ], {})
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed40") ] } }, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed41") ] } }, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed42") ] } }, { fields: {} })
[
{
"_id": "596219ff6f73ed36d868ed43",
"title": "New News",
"__v": 0,
"ratings": [
{
"_id": "596219ff6f73ed36d868ed44",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed40",
"name": "Bill",
"email": "bill@example.com",
"__v": 0
},
"rating": "5",
"comment": "Great!",
"__v": 0
},
{
"_id": "596219ff6f73ed36d868ed45",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed41",
"name": "Fred",
"email": "fred@example.com",
"__v": 0
},
"rating": "3",
"comment": "Okay I guess ;)",
"__v": 0
},
{
"_id": "596219ff6f73ed36d868ed46",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed42",
"name": "Ted",
"email": "ted@example.com",
"__v": 0
},
"rating": "1",
"comment": "Hated it :<",
"__v": 0
}
]
}
]
Mongoose: articles.aggregate([ { '$lookup': { from: 'articleratings', localField: '_id', foreignField: 'articleId', as: 'ratings' } } ], {})
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed40") ] } }, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed41") ] } }, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed42") ] } }, { fields: {} })
[
{
"_id": "596219ff6f73ed36d868ed43",
"title": "New News",
"__v": 0,
"ratings": [
{
"_id": "596219ff6f73ed36d868ed44",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed40",
"name": "Bill",
"email": "bill@example.com",
"__v": 0
},
"rating": "5",
"comment": "Great!",
"__v": 0
},
{
"_id": "596219ff6f73ed36d868ed45",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed41",
"name": "Fred",
"email": "fred@example.com",
"__v": 0
},
"rating": "3",
"comment": "Okay I guess ;)",
"__v": 0
},
{
"_id": "596219ff6f73ed36d868ed46",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed42",
"name": "Ted",
"email": "ted@example.com",
"__v": 0
},
"rating": "1",
"comment": "Hated it :<",
"__v": 0
}
]
}
]
Mongoose: articles.aggregate([ { '$lookup': { from: 'articleratings', localField: '_id', foreignField: 'articleId', as: 'ratings' } }, { '$unwind': '$ratings' }, { '$lookup': { from: 'users', localField: 'ratings.userId', foreignField: '_id', as: 'ratings.userId' } }, { '$unwind': '$ratings.userId' }, { '$group': { _id: '$_id', title: { '$first': '$title' }, ratings: { '$push': '$ratings' } } } ], {})
[
{
"_id": "596219ff6f73ed36d868ed43",
"title": "New News",
"ratings": [
{
"_id": "596219ff6f73ed36d868ed44",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed40",
"name": "Bill",
"email": "bill@example.com",
"__v": 0
},
"rating": "5",
"comment": "Great!",
"__v": 0
},
{
"_id": "596219ff6f73ed36d868ed45",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed41",
"name": "Fred",
"email": "fred@example.com",
"__v": 0
},
"rating": "3",
"comment": "Okay I guess ;)",
"__v": 0
},
{
"_id": "596219ff6f73ed36d868ed46",
"articleId": "596219ff6f73ed36d868ed43",
"userId": {
"_id": "596219ff6f73ed36d868ed42",
"name": "Ted",
"email": "ted@example.com",
"__v": 0
},
"rating": "1",
"comment": "Hated it :<",
"__v": 0
}
]
}
]